mirror of
https://github.com/BobbyWibowo/lolisafe.git
synced 2025-01-18 17:21:33 +00:00
Updated
Added delete user feature. API: /api/users/delete json: id<number>, purge[boolean] By default will not purge out files, but will still clear userid attribute from the files. All associated albums will also be marked, and have their ZIP archives be unliked, if applicable. Fixed purging albums not properly reporting amount of associated files that could not be removed, if any. Fixed moderators being able to disable users by manually sending API requests, if they at least know of the user IDs. They could only disable regular users however.
This commit is contained in:
parent
5e60b01fe6
commit
4f04225ba0
@ -183,6 +183,7 @@ self.delete = async (req, res, next) => {
|
||||
if (failed.length)
|
||||
return res.json({ success: false, failed })
|
||||
}
|
||||
utils.invalidateStatsCache('uploads')
|
||||
}
|
||||
|
||||
await db.table('albums')
|
||||
@ -245,53 +246,48 @@ self.edit = async (req, res, next) => {
|
||||
// Old rename API
|
||||
return res.json({ success: false, description: 'You did not specify a new name.' })
|
||||
|
||||
await db.table('albums')
|
||||
.where({
|
||||
id,
|
||||
userid: user.id
|
||||
})
|
||||
.update({
|
||||
name,
|
||||
editedAt: Math.floor(Date.now() / 1000),
|
||||
download: Boolean(req.body.download),
|
||||
public: Boolean(req.body.public),
|
||||
description: typeof req.body.description === 'string'
|
||||
? utils.escape(req.body.description.trim().substring(0, self.descMaxLength))
|
||||
: ''
|
||||
})
|
||||
utils.invalidateAlbumsCache([id])
|
||||
|
||||
if (!req.body.requestLink)
|
||||
return res.json({ success: true, name })
|
||||
|
||||
const oldIdentifier = album.identifier
|
||||
const newIdentifier = await self.getUniqueRandomName()
|
||||
|
||||
await db.table('albums')
|
||||
.where({
|
||||
id,
|
||||
userid: user.id
|
||||
})
|
||||
.update('identifier', newIdentifier)
|
||||
utils.invalidateStatsCache('albums')
|
||||
self.onHold.delete(newIdentifier)
|
||||
|
||||
// Rename zip archive of the album if it exists
|
||||
try {
|
||||
const oldZip = path.join(paths.zips, `${oldIdentifier}.zip`)
|
||||
// await paths.access(oldZip)
|
||||
const newZip = path.join(paths.zips, `${newIdentifier}.zip`)
|
||||
await paths.rename(oldZip, newZip)
|
||||
} catch (err) {
|
||||
// Re-throw error
|
||||
if (err.code !== 'ENOENT')
|
||||
throw err
|
||||
const update = {
|
||||
name,
|
||||
editedAt: Math.floor(Date.now() / 1000),
|
||||
download: Boolean(req.body.download),
|
||||
public: Boolean(req.body.public),
|
||||
description: typeof req.body.description === 'string'
|
||||
? utils.escape(req.body.description.trim().substring(0, self.descMaxLength))
|
||||
: ''
|
||||
}
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
identifier: newIdentifier
|
||||
})
|
||||
if (req.body.requestLink)
|
||||
update.identifier = await self.getUniqueRandomName()
|
||||
|
||||
await db.table('albums')
|
||||
.where({
|
||||
id,
|
||||
userid: user.id
|
||||
})
|
||||
.update(update)
|
||||
utils.invalidateAlbumsCache([id])
|
||||
|
||||
if (req.body.requestLink) {
|
||||
self.onHold.delete(update.identifier)
|
||||
|
||||
// Rename zip archive of the album if it exists
|
||||
try {
|
||||
const oldZip = path.join(paths.zips, `${album.identifier}.zip`)
|
||||
const newZip = path.join(paths.zips, `${update.identifier}.zip`)
|
||||
await paths.rename(oldZip, newZip)
|
||||
} catch (err) {
|
||||
// Re-throw error
|
||||
if (err.code !== 'ENOENT')
|
||||
throw err
|
||||
}
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
identifier: update.identifier
|
||||
})
|
||||
} else {
|
||||
return res.json({ success: true, name })
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(error)
|
||||
return res.status(500).json({ success: false, description: 'An unexpected error occurred. Try again?' })
|
||||
|
@ -1,5 +1,7 @@
|
||||
const bcrypt = require('bcrypt')
|
||||
const path = require('path')
|
||||
const randomstring = require('randomstring')
|
||||
const paths = require('./pathsController')
|
||||
const perms = require('./permissionController')
|
||||
const tokens = require('./tokenController')
|
||||
const utils = require('./utilsController')
|
||||
@ -135,10 +137,23 @@ self.changePassword = async (req, res, next) => {
|
||||
}
|
||||
}
|
||||
|
||||
self.assertPermission = (user, target) => {
|
||||
if (!target)
|
||||
throw new Error('Could not get user with the specified ID.')
|
||||
else if (!perms.higher(user, target))
|
||||
throw new Error('The user is in the same or higher group as you.')
|
||||
else if (target.username === 'root')
|
||||
throw new Error('Root user may not be tampered with.')
|
||||
}
|
||||
|
||||
self.editUser = async (req, res, next) => {
|
||||
const user = await utils.authorize(req, res)
|
||||
if (!user) return
|
||||
|
||||
const isadmin = perms.is(user, 'admin')
|
||||
if (!isadmin)
|
||||
return res.status(403).end()
|
||||
|
||||
const id = parseInt(req.body.id)
|
||||
if (isNaN(id))
|
||||
return res.json({ success: false, description: 'No user specified.' })
|
||||
@ -147,23 +162,14 @@ self.editUser = async (req, res, next) => {
|
||||
const target = await db.table('users')
|
||||
.where('id', id)
|
||||
.first()
|
||||
|
||||
if (!target)
|
||||
return res.json({ success: false, description: 'Could not get user with the specified ID.' })
|
||||
else if (!perms.higher(user, target))
|
||||
return res.json({ success: false, description: 'The user is in the same or higher group as you.' })
|
||||
else if (target.username === 'root')
|
||||
return res.json({ success: false, description: 'Root user may not be edited.' })
|
||||
self.assertPermission(user, target)
|
||||
|
||||
const update = {}
|
||||
|
||||
if (req.body.username !== undefined) {
|
||||
update.username = String(req.body.username).trim()
|
||||
if (update.username.length < self.user.min || update.username.length > self.user.max)
|
||||
return res.json({
|
||||
success: false,
|
||||
description: `Username must have ${self.user.min}-${self.user.max} characters.`
|
||||
})
|
||||
throw new Error(`Username must have ${self.user.min}-${self.user.max} characters.`)
|
||||
}
|
||||
|
||||
if (req.body.enabled !== undefined)
|
||||
@ -191,7 +197,10 @@ self.editUser = async (req, res, next) => {
|
||||
return res.json(response)
|
||||
} catch (error) {
|
||||
logger.error(error)
|
||||
return res.status(500).json({ success: false, description: 'An unexpected error occurred. Try again?' })
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
description: error.message || 'An unexpected error occurred. Try again?'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -200,6 +209,86 @@ self.disableUser = async (req, res, next) => {
|
||||
return self.editUser(req, res, next)
|
||||
}
|
||||
|
||||
self.deleteUser = async (req, res, next) => {
|
||||
const user = await utils.authorize(req, res)
|
||||
if (!user) return
|
||||
|
||||
const isadmin = perms.is(user, 'admin')
|
||||
if (!isadmin)
|
||||
return res.status(403).end()
|
||||
|
||||
const id = parseInt(req.body.id)
|
||||
const purge = req.body.purge
|
||||
if (isNaN(id))
|
||||
return res.json({ success: false, description: 'No user specified.' })
|
||||
|
||||
try {
|
||||
const target = await db.table('users')
|
||||
.where('id', id)
|
||||
.first()
|
||||
self.assertPermission(user, target)
|
||||
|
||||
const files = await db.table('files')
|
||||
.where('userid', id)
|
||||
.select('id')
|
||||
|
||||
if (files.length) {
|
||||
const fileids = files.map(file => file.id)
|
||||
if (purge) {
|
||||
const failed = await utils.bulkDeleteFromDb('id', fileids, user)
|
||||
if (failed.length)
|
||||
return res.json({ success: false, failed })
|
||||
utils.invalidateStatsCache('uploads')
|
||||
} else {
|
||||
// Clear out userid attribute from the files
|
||||
await db.table('files')
|
||||
.whereIn('id', fileids)
|
||||
.update('userid', null)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Figure out obstacles of just deleting the albums
|
||||
const albums = await db.table('albums')
|
||||
.where('userid', id)
|
||||
.where('enabled', 1)
|
||||
.select('id', 'identifier')
|
||||
|
||||
if (albums.length) {
|
||||
const albumids = albums.map(album => album.id)
|
||||
await db.table('albums')
|
||||
.whereIn('id', albumids)
|
||||
.del()
|
||||
utils.invalidateAlbumsCache(albumids)
|
||||
|
||||
// Unlink their archives
|
||||
await Promise.all(albums.map(async album => {
|
||||
try {
|
||||
await paths.unlink(path.join(paths.zips, `${album.identifier}.zip`))
|
||||
} catch (error) {
|
||||
if (error.code !== 'ENOENT')
|
||||
throw error
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
await db.table('users')
|
||||
.where('id', id)
|
||||
.del()
|
||||
utils.invalidateStatsCache('users')
|
||||
|
||||
return res.json({ success: true })
|
||||
} catch (error) {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
description: error.message || 'An unexpected error occurred. Try again?'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
self.bulkDeleteUsers = async (req, res, next) => {
|
||||
// TODO
|
||||
}
|
||||
|
||||
self.listUsers = async (req, res, next) => {
|
||||
const user = await utils.authorize(req, res)
|
||||
if (!user) return
|
||||
|
2
dist/css/dashboard.css
vendored
2
dist/css/dashboard.css
vendored
@ -1,2 +1,2 @@
|
||||
body{-webkit-animation:none;animation:none}#dashboard{-webkit-animation:fadeInOpacity .5s;animation:fadeInOpacity .5s}.section{background:none}.menu-list a{color:#209cee;border:1px solid transparent;margin-top:-1px}.menu-list a.is-active{color:#fff;background:#209cee;border-color:#209cee}.menu-list a:not(.is-active):hover{color:#209cee;background:none;border-color:#209cee}.menu-list a[disabled]{color:#7a7a7a;pointer-events:none}.menu-list a.is-loading:after{-webkit-animation:spinAround .5s linear infinite;animation:spinAround .5s linear infinite;border-radius:290486px;border-color:transparent transparent #dbdbdb #dbdbdb;border-style:solid;border-width:2px;content:"";display:block;height:1em;width:1em;right:.5em;top:calc(50% - .5em);position:absolute!important}ul#albumsContainer{border-left:0;padding-left:0}ul#albumsContainer li{border-left:2px solid #585858;padding-left:.75em}#page.fade-in,ul#albumsContainer li{-webkit-animation:fadeInOpacity .5s;animation:fadeInOpacity .5s}.pagination{margin-bottom:1.25rem}.pagination a:not([disabled]){color:#eff0f1;border-color:#eff0f1;background:none}.pagination a.pagination-link:hover,.pagination a.pagination-next:not([disabled]):hover,.pagination a.pagination-previous:not([disabled]):hover{color:#000;background-color:#eff0f1;border-color:#eff0f1}.pagination a.pagination-link.is-current{color:#000;background-color:#eff0f1}.pagination a.is-loading:hover:after,.pagination a.pagination-link.is-current.is-loading:after{border-bottom-color:#000;border-left-color:#000}li[data-action=page-ellipsis]{cursor:pointer}.label{color:#bdc3c7}.menu-list li ul{border-left-color:#898b8d}.image-container .checkbox{position:absolute;top:11px;left:11px}.image-container .controls{display:flex;position:absolute;top:11px;right:11px}.image-container .controls .button{border-radius:0}.image-container .controls .button:not(:active):not(:hover){color:#fff;background-color:rgba(0,0,0,.56078)}.no-touch .image-container .checkbox{opacity:.5}.no-touch .image-container .controls,.no-touch .image-container .details{opacity:0}.no-touch .image-container:hover .checkbox,.no-touch .image-container:hover .controls,.no-touch .image-container:hover .details{opacity:1}#page{min-width:0}.table{color:#bdc3c7;background-color:#000;font-size:.75rem}.table.is-hoverable tbody tr:not(.is-selected):hover{background-color:#2f2f2f}.table td,.table th{white-space:nowrap;vertical-align:middle;border-bottom:1px solid #585858}.table th{color:#eff0f1;height:2.25em;font-weight:400}.table thead td,.table thead th{color:#eff0f1;background-color:#585858;border-bottom:0;height:33px}.table tbody tr:last-child td,.table tbody tr:last-child th{border-bottom-width:1px}.table .cell-indent{padding-left:2.25em}.is-linethrough{text-decoration:line-through}#menu.is-loading .menu-list a{cursor:progress}#statistics tr :first-child{width:50%}.expirydate{color:#bdc3c7}
|
||||
body{-webkit-animation:none;animation:none}#dashboard{-webkit-animation:fadeInOpacity .5s;animation:fadeInOpacity .5s}.section{background:none}.menu-list a{color:#209cee;border:1px solid transparent;margin-top:-1px}.menu-list a.is-active{color:#fff;background:#209cee;border-color:#209cee}.menu-list a:not(.is-active):hover{color:#209cee;background:none;border-color:#209cee}.menu-list a[disabled]{color:#7a7a7a;pointer-events:none}.menu-list a.is-loading:after{-webkit-animation:spinAround .5s linear infinite;animation:spinAround .5s linear infinite;border-radius:290486px;border-color:transparent transparent #dbdbdb #dbdbdb;border-style:solid;border-width:2px;content:"";display:block;height:1em;width:1em;right:.5em;top:calc(50% - .5em);position:absolute!important}ul#albumsContainer{border-left:0;padding-left:0}ul#albumsContainer li{border-left:2px solid #585858;padding-left:.75em}#page.fade-in,ul#albumsContainer li{-webkit-animation:fadeInOpacity .5s;animation:fadeInOpacity .5s}.pagination{margin-bottom:1.25rem}.pagination a:not([disabled]){color:#eff0f1;border-color:#eff0f1;background:none}.pagination a.pagination-link:hover,.pagination a.pagination-next:not([disabled]):hover,.pagination a.pagination-previous:not([disabled]):hover{color:#000;background-color:#eff0f1;border-color:#eff0f1}.pagination a.pagination-link.is-current{color:#000;background-color:#eff0f1}.pagination a.is-loading:hover:after,.pagination a.pagination-link.is-current.is-loading:after{border-bottom-color:#000;border-left-color:#000}li[data-action=page-ellipsis]{cursor:pointer}.label{color:#bdc3c7}.menu-list li ul{border-left-color:#898b8d}.image-container .checkbox{position:absolute;top:11px;left:11px}.image-container .controls{display:flex;position:absolute;top:11px;right:11px}.image-container .controls .button{border-radius:0}.image-container .controls .button:not(:active):not(:hover){color:#fff;background-color:rgba(0,0,0,.56078)}.no-touch .image-container .checkbox{opacity:.5}.no-touch .image-container .controls,.no-touch .image-container .details{opacity:0}.no-touch .image-container:hover .checkbox,.no-touch .image-container:hover .controls,.no-touch .image-container:hover .details{opacity:1}#page{min-width:0}.table{color:#bdc3c7;background-color:#000;font-size:.75rem}.table.is-hoverable tbody tr:not(.is-selected):hover{background-color:#2f2f2f}.table td,.table th{white-space:nowrap;vertical-align:middle;border-bottom:1px solid #585858}.table th{color:#eff0f1;height:2.25em;font-weight:400}.table thead td,.table thead th{color:#eff0f1;background-color:#585858;border-bottom:0;height:31px}.table tbody tr:last-child td,.table tbody tr:last-child th{border-bottom-width:1px}.table .cell-indent{padding-left:2.25em}.is-linethrough{text-decoration:line-through}#menu.is-loading .menu-list a{cursor:progress}#statistics tr :first-child{width:50%}.expirydate{color:#bdc3c7}
|
||||
/*# sourceMappingURL=dashboard.css.map */
|
||||
|
2
dist/css/dashboard.css.map
vendored
2
dist/css/dashboard.css.map
vendored
@ -1 +1 @@
|
||||
{"version":3,"sources":["css/dashboard.css"],"names":[],"mappings":"AAAA,KACE,sBAAc,CAAd,cACF,CAEA,WACE,mCAA4B,CAA5B,2BACF,CAEA,SACE,eACF,CAEA,aACE,aAAc,CACd,4BAA6B,CAC7B,eACF,CAEA,uBACE,UAAW,CACX,kBAAmB,CACnB,oBACF,CAEA,mCACE,aAAc,CACd,eAAgB,CAChB,oBACF,CAEA,uBACE,aAAc,CACd,mBACF,CAEA,8BACE,gDAA0C,CAA1C,wCAA0C,CAE1C,sBAAuB,CAEvB,oDAA6B,CAA7B,kBAA6B,CAA7B,gBAA6B,CAC7B,UAAW,CACX,aAAc,CACd,UAAW,CACX,SAAU,CACV,UAA2B,CAC3B,oBAA0B,CAC1B,2BACF,CAEA,mBACE,aAAc,CACd,cACF,CAEA,sBACE,6BAA8B,CAC9B,kBAEF,CAEA,oCAHE,mCAA4B,CAA5B,2BAKF,CAEA,YACE,qBACF,CAEA,8BACE,aAAc,CACd,oBAAqB,CACrB,eACF,CAEA,gJAGE,UAAW,CACX,wBAAyB,CACzB,oBACF,CAEA,yCACE,UAAW,CACX,wBACF,CAEA,+FAEE,wBAAyB,CACzB,sBACF,CAEA,8BACE,cACF,CAEA,OACE,aACF,CAEA,iBACE,yBACF,CAEA,2BACE,iBAAkB,CAClB,QAAS,CACT,SACF,CAEA,2BACE,YAAa,CACb,iBAAkB,CAClB,QAAS,CACT,UACF,CAEA,mCACE,eACF,CAEA,4DACE,UAAW,CACX,mCACF,CAEA,qCACE,UACF,CAEA,yEAEE,SACF,CAEA,gIAGE,SACF,CAEA,MAEE,WACF,CAEA,OACE,aAAc,CACd,qBAAsB,CACtB,gBACF,CAEA,qDACE,wBACF,CAEA,oBAEE,kBAAmB,CACnB,qBAAsB,CACtB,+BACF,CAEA,UACE,aAAc,CACd,aAAc,CACd,eACF,CAEA,gCAEE,aAAc,CACd,wBAAyB,CACzB,eAAgB,CAChB,WACF,CAEA,4DAEE,uBACF,CAEA,oBACE,mBACF,CAEA,gBACE,4BACF,CAEA,8BACE,eACF,CAEA,4BACE,SACF,CAEA,YACE,aACF","file":"dashboard.css","sourcesContent":["body {\n animation: none\n}\n\n#dashboard {\n animation: fadeInOpacity 0.5s\n}\n\n.section {\n background: none\n}\n\n.menu-list a {\n color: #209cee;\n border: 1px solid transparent;\n margin-top: -1px\n}\n\n.menu-list a.is-active {\n color: #fff;\n background: #209cee;\n border-color: #209cee\n}\n\n.menu-list a:not(.is-active):hover {\n color: #209cee;\n background: none;\n border-color: #209cee\n}\n\n.menu-list a[disabled] {\n color: #7a7a7a;\n pointer-events: none\n}\n\n.menu-list a.is-loading::after {\n animation: spinAround 0.5s infinite linear;\n border: 2px solid #dbdbdb;\n border-radius: 290486px;\n border-right-color: transparent;\n border-top-color: transparent;\n content: \"\";\n display: block;\n height: 1em;\n width: 1em;\n right: calc(0% + (1em / 2));\n top: calc(50% - (1em / 2));\n position: absolute !important\n}\n\nul#albumsContainer {\n border-left: 0;\n padding-left: 0\n}\n\nul#albumsContainer li {\n border-left: 2px solid #585858;\n padding-left: 0.75em;\n animation: fadeInOpacity 0.5s\n}\n\n#page.fade-in {\n animation: fadeInOpacity 0.5s\n}\n\n.pagination {\n margin-bottom: 1.25rem\n}\n\n.pagination a:not([disabled]) {\n color: #eff0f1;\n border-color: #eff0f1;\n background: none\n}\n\n.pagination a.pagination-link:hover,\n.pagination a.pagination-next:not([disabled]):hover,\n.pagination a.pagination-previous:not([disabled]):hover {\n color: #000;\n background-color: #eff0f1;\n border-color: #eff0f1\n}\n\n.pagination a.pagination-link.is-current {\n color: #000;\n background-color: #eff0f1\n}\n\n.pagination a.is-loading:hover::after,\n.pagination a.pagination-link.is-current.is-loading::after {\n border-bottom-color: #000;\n border-left-color: #000\n}\n\nli[data-action=\"page-ellipsis\"] {\n cursor: pointer\n}\n\n.label {\n color: #bdc3c7\n}\n\n.menu-list li ul {\n border-left-color: #898b8d\n}\n\n.image-container .checkbox {\n position: absolute;\n top: 11px;\n left: 11px\n}\n\n.image-container .controls {\n display: flex;\n position: absolute;\n top: 11px;\n right: 11px\n}\n\n.image-container .controls .button {\n border-radius: 0\n}\n\n.image-container .controls .button:not(:active):not(:hover) {\n color: #fff;\n background-color: #0000008f\n}\n\n.no-touch .image-container .checkbox {\n opacity: 0.5\n}\n\n.no-touch .image-container .controls,\n.no-touch .image-container .details {\n opacity: 0\n}\n\n.no-touch .image-container:hover .checkbox,\n.no-touch .image-container:hover .controls,\n.no-touch .image-container:hover .details {\n opacity: 1\n}\n\n#page {\n /* fix overflow issue with flex */\n min-width: 0\n}\n\n.table {\n color: #bdc3c7;\n background-color: #000;\n font-size: 0.75rem\n}\n\n.table.is-hoverable tbody tr:not(.is-selected):hover {\n background-color: #2f2f2f\n}\n\n.table td,\n.table th {\n white-space: nowrap;\n vertical-align: middle;\n border-bottom: 1px solid #585858\n}\n\n.table th {\n color: #eff0f1;\n height: 2.25em;\n font-weight: normal\n}\n\n.table thead td,\n.table thead th {\n color: #eff0f1;\n background-color: #585858;\n border-bottom: 0;\n height: 33px\n}\n\n.table tbody tr:last-child td,\n.table tbody tr:last-child th {\n border-bottom-width: 1px\n}\n\n.table .cell-indent {\n padding-left: 2.25em\n}\n\n.is-linethrough {\n text-decoration: line-through\n}\n\n#menu.is-loading .menu-list a {\n cursor: progress\n}\n\n#statistics tr *:nth-child(1) {\n width: 50%\n}\n\n.expirydate {\n color: #bdc3c7\n}\n"]}
|
||||
{"version":3,"sources":["css/dashboard.css"],"names":[],"mappings":"AAAA,KACE,sBAAc,CAAd,cACF,CAEA,WACE,mCAA4B,CAA5B,2BACF,CAEA,SACE,eACF,CAEA,aACE,aAAc,CACd,4BAA6B,CAC7B,eACF,CAEA,uBACE,UAAW,CACX,kBAAmB,CACnB,oBACF,CAEA,mCACE,aAAc,CACd,eAAgB,CAChB,oBACF,CAEA,uBACE,aAAc,CACd,mBACF,CAEA,8BACE,gDAA0C,CAA1C,wCAA0C,CAE1C,sBAAuB,CAEvB,oDAA6B,CAA7B,kBAA6B,CAA7B,gBAA6B,CAC7B,UAAW,CACX,aAAc,CACd,UAAW,CACX,SAAU,CACV,UAA2B,CAC3B,oBAA0B,CAC1B,2BACF,CAEA,mBACE,aAAc,CACd,cACF,CAEA,sBACE,6BAA8B,CAC9B,kBAEF,CAEA,oCAHE,mCAA4B,CAA5B,2BAKF,CAEA,YACE,qBACF,CAEA,8BACE,aAAc,CACd,oBAAqB,CACrB,eACF,CAEA,gJAGE,UAAW,CACX,wBAAyB,CACzB,oBACF,CAEA,yCACE,UAAW,CACX,wBACF,CAEA,+FAEE,wBAAyB,CACzB,sBACF,CAEA,8BACE,cACF,CAEA,OACE,aACF,CAEA,iBACE,yBACF,CAEA,2BACE,iBAAkB,CAClB,QAAS,CACT,SACF,CAEA,2BACE,YAAa,CACb,iBAAkB,CAClB,QAAS,CACT,UACF,CAEA,mCACE,eACF,CAEA,4DACE,UAAW,CACX,mCACF,CAEA,qCACE,UACF,CAEA,yEAEE,SACF,CAEA,gIAGE,SACF,CAEA,MAEE,WACF,CAEA,OACE,aAAc,CACd,qBAAsB,CACtB,gBACF,CAEA,qDACE,wBACF,CAEA,oBAEE,kBAAmB,CACnB,qBAAsB,CACtB,+BACF,CAEA,UACE,aAAc,CACd,aAAc,CACd,eACF,CAEA,gCAEE,aAAc,CACd,wBAAyB,CACzB,eAAgB,CAChB,WACF,CAEA,4DAEE,uBACF,CAEA,oBACE,mBACF,CAEA,gBACE,4BACF,CAEA,8BACE,eACF,CAEA,4BACE,SACF,CAEA,YACE,aACF","file":"dashboard.css","sourcesContent":["body {\n animation: none\n}\n\n#dashboard {\n animation: fadeInOpacity 0.5s\n}\n\n.section {\n background: none\n}\n\n.menu-list a {\n color: #209cee;\n border: 1px solid transparent;\n margin-top: -1px\n}\n\n.menu-list a.is-active {\n color: #fff;\n background: #209cee;\n border-color: #209cee\n}\n\n.menu-list a:not(.is-active):hover {\n color: #209cee;\n background: none;\n border-color: #209cee\n}\n\n.menu-list a[disabled] {\n color: #7a7a7a;\n pointer-events: none\n}\n\n.menu-list a.is-loading::after {\n animation: spinAround 0.5s infinite linear;\n border: 2px solid #dbdbdb;\n border-radius: 290486px;\n border-right-color: transparent;\n border-top-color: transparent;\n content: \"\";\n display: block;\n height: 1em;\n width: 1em;\n right: calc(0% + (1em / 2));\n top: calc(50% - (1em / 2));\n position: absolute !important\n}\n\nul#albumsContainer {\n border-left: 0;\n padding-left: 0\n}\n\nul#albumsContainer li {\n border-left: 2px solid #585858;\n padding-left: 0.75em;\n animation: fadeInOpacity 0.5s\n}\n\n#page.fade-in {\n animation: fadeInOpacity 0.5s\n}\n\n.pagination {\n margin-bottom: 1.25rem\n}\n\n.pagination a:not([disabled]) {\n color: #eff0f1;\n border-color: #eff0f1;\n background: none\n}\n\n.pagination a.pagination-link:hover,\n.pagination a.pagination-next:not([disabled]):hover,\n.pagination a.pagination-previous:not([disabled]):hover {\n color: #000;\n background-color: #eff0f1;\n border-color: #eff0f1\n}\n\n.pagination a.pagination-link.is-current {\n color: #000;\n background-color: #eff0f1\n}\n\n.pagination a.is-loading:hover::after,\n.pagination a.pagination-link.is-current.is-loading::after {\n border-bottom-color: #000;\n border-left-color: #000\n}\n\nli[data-action=\"page-ellipsis\"] {\n cursor: pointer\n}\n\n.label {\n color: #bdc3c7\n}\n\n.menu-list li ul {\n border-left-color: #898b8d\n}\n\n.image-container .checkbox {\n position: absolute;\n top: 11px;\n left: 11px\n}\n\n.image-container .controls {\n display: flex;\n position: absolute;\n top: 11px;\n right: 11px\n}\n\n.image-container .controls .button {\n border-radius: 0\n}\n\n.image-container .controls .button:not(:active):not(:hover) {\n color: #fff;\n background-color: #0000008f\n}\n\n.no-touch .image-container .checkbox {\n opacity: 0.5\n}\n\n.no-touch .image-container .controls,\n.no-touch .image-container .details {\n opacity: 0\n}\n\n.no-touch .image-container:hover .checkbox,\n.no-touch .image-container:hover .controls,\n.no-touch .image-container:hover .details {\n opacity: 1\n}\n\n#page {\n /* fix overflow issue with flex */\n min-width: 0\n}\n\n.table {\n color: #bdc3c7;\n background-color: #000;\n font-size: 0.75rem\n}\n\n.table.is-hoverable tbody tr:not(.is-selected):hover {\n background-color: #2f2f2f\n}\n\n.table td,\n.table th {\n white-space: nowrap;\n vertical-align: middle;\n border-bottom: 1px solid #585858\n}\n\n.table th {\n color: #eff0f1;\n height: 2.25em;\n font-weight: normal\n}\n\n.table thead td,\n.table thead th {\n color: #eff0f1;\n background-color: #585858;\n border-bottom: 0;\n height: 31px\n}\n\n.table tbody tr:last-child td,\n.table tbody tr:last-child th {\n border-bottom-width: 1px\n}\n\n.table .cell-indent {\n padding-left: 2.25em\n}\n\n.is-linethrough {\n text-decoration: line-through\n}\n\n#menu.is-loading .menu-list a {\n cursor: progress\n}\n\n#statistics tr *:nth-child(1) {\n width: 50%\n}\n\n.expirydate {\n color: #bdc3c7\n}\n"]}
|
2
dist/js/dashboard.js
vendored
2
dist/js/dashboard.js
vendored
File diff suppressed because one or more lines are too long
2
dist/js/dashboard.js.map
vendored
2
dist/js/dashboard.js.map
vendored
File diff suppressed because one or more lines are too long
@ -48,6 +48,7 @@ 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))
|
||||
routes.post('/users/delete', (req, res, next) => authController.deleteUser(req, res, next))
|
||||
routes.get('/stats', (req, res, next) => utilsController.stats(req, res, next))
|
||||
|
||||
module.exports = routes
|
||||
|
@ -174,7 +174,7 @@ li[data-action="page-ellipsis"] {
|
||||
color: #eff0f1;
|
||||
background-color: #585858;
|
||||
border-bottom: 0;
|
||||
height: 33px
|
||||
height: 31px
|
||||
}
|
||||
|
||||
.table tbody tr:last-child td,
|
||||
|
@ -329,6 +329,8 @@ page.domClick = event => {
|
||||
return page.editUser(id)
|
||||
case 'disable-user':
|
||||
return page.disableUser(id)
|
||||
case 'delete-user':
|
||||
return page.deleteUser(id)
|
||||
case 'filters-help':
|
||||
return page.filtersHelp(element)
|
||||
case 'filter-uploads':
|
||||
@ -743,6 +745,7 @@ page.getUploads = (params = {}) => {
|
||||
page.setUploadsView = (view, element) => {
|
||||
localStorage[lsKeys.viewType[page.currentView]] = view
|
||||
page.views[page.currentView].type = view
|
||||
|
||||
// eslint-disable-next-line compat/compat
|
||||
page.getUploads(Object.assign({
|
||||
trigger: element
|
||||
@ -964,7 +967,7 @@ page.filtersHelp = element => {
|
||||
}
|
||||
|
||||
page.filterUploads = element => {
|
||||
const filters = document.querySelector('#filters').value
|
||||
const filters = document.querySelector('#filters').value.trim()
|
||||
page.getUploads({ all: true, filters }, element)
|
||||
}
|
||||
|
||||
@ -1516,14 +1519,18 @@ page.deleteAlbum = id => {
|
||||
id,
|
||||
purge: proceed === 'purge'
|
||||
}).then(response => {
|
||||
if (response.data.success === false)
|
||||
if (response.data.description === 'No token provided') {
|
||||
if (response.data.success === false) {
|
||||
const failed = Array.isArray(response.data.failed)
|
||||
? response.data.failed
|
||||
: []
|
||||
|
||||
if (response.data.description === 'No token provided')
|
||||
return page.verifyToken(page.token)
|
||||
} else if (Array.isArray(response.data.failed) && response.data.failed.length) {
|
||||
return swal('An error occurred!', 'Unable to delete ', 'error')
|
||||
} else {
|
||||
else if (failed.length)
|
||||
return swal('An error occurred!', `Unable to delete ${failed.length} of the album's upload${failed.length === 1 ? '' : 's'}.`, 'error')
|
||||
else
|
||||
return swal('An error occurred!', response.data.description, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
swal('Deleted!', 'Your album has been deleted.', 'success')
|
||||
page.getAlbumsSidebar()
|
||||
@ -1746,11 +1753,14 @@ page.getUsers = (params = {}) => {
|
||||
return swal('An error occurred!', response.data.description, 'error')
|
||||
}
|
||||
|
||||
if (params.pageNum && (response.data.users.length === 0)) {
|
||||
// Only remove loading class here, since beyond this the entire page will be replaced anyways
|
||||
page.updateTrigger(params.trigger)
|
||||
return swal('An error occurred!', `There are no more users to populate page ${params.pageNum + 1}.`, 'error')
|
||||
}
|
||||
if (params.pageNum && (response.data.users.length === 0))
|
||||
if (params.autoPage) {
|
||||
params.pageNum = Math.ceil(response.data.count / 25) - 1
|
||||
return page.getUsers(params)
|
||||
} else {
|
||||
page.updateTrigger(params.trigger)
|
||||
return swal('An error occurred!', `There are no more users to populate page ${params.pageNum + 1}.`, 'error')
|
||||
}
|
||||
|
||||
page.currentView = 'users'
|
||||
page.cache.users = {}
|
||||
@ -1828,7 +1838,6 @@ page.getUsers = (params = {}) => {
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<hr>
|
||||
${pagination}
|
||||
`
|
||||
|
||||
@ -1880,7 +1889,7 @@ page.getUsers = (params = {}) => {
|
||||
<i class="icon-hammer"></i>
|
||||
</span>
|
||||
</a>
|
||||
<a class="button is-small is-danger is-outlined is-hidden" title="Delete user (WIP)" data-action="delete-user" disabled>
|
||||
<a class="button is-small is-danger is-outlined" title="Delete user" data-action="delete-user">
|
||||
<span class="icon">
|
||||
<i class="icon-trash"></i>
|
||||
</span>
|
||||
@ -2010,7 +2019,10 @@ page.disableUser = 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>.`
|
||||
content.innerHTML = `
|
||||
<p>You will be disabling a user named <b>${page.cache.users[id].username}</b>.</p>
|
||||
<p>Their files will remain.</p>
|
||||
`
|
||||
|
||||
swal({
|
||||
title: 'Are you sure?',
|
||||
@ -2036,12 +2048,73 @@ page.disableUser = id => {
|
||||
else
|
||||
return swal('An error occurred!', response.data.description, 'error')
|
||||
|
||||
swal('Success!', 'The user has been disabled.', 'success')
|
||||
swal('Success!', `${page.cache.users[id].username} has been disabled.`, 'success')
|
||||
page.getUsers(page.views.users)
|
||||
}).catch(page.onAxiosError)
|
||||
})
|
||||
}
|
||||
|
||||
page.deleteUser = id => {
|
||||
const user = page.cache.users[id]
|
||||
if (!user || !user.enabled) return
|
||||
|
||||
const content = document.createElement('div')
|
||||
content.innerHTML = `
|
||||
<p>You will be deleting a user named <b>${page.cache.users[id].username}</b>.<p>
|
||||
<p>Their files will remain, unless you choose otherwise.</p>
|
||||
`
|
||||
|
||||
swal({
|
||||
title: 'Are you sure?',
|
||||
icon: 'warning',
|
||||
content,
|
||||
dangerMode: true,
|
||||
buttons: {
|
||||
cancel: true,
|
||||
confirm: {
|
||||
text: 'Yes, delete it!',
|
||||
closeModal: false
|
||||
},
|
||||
purge: {
|
||||
text: 'Yes, and the uploads too!',
|
||||
value: 'purge',
|
||||
className: 'swal-button--danger',
|
||||
closeModal: false
|
||||
}
|
||||
}
|
||||
}).then(proceed => {
|
||||
if (!proceed) return
|
||||
|
||||
axios.post('api/users/delete', {
|
||||
id,
|
||||
purge: proceed === 'purge'
|
||||
}).then(response => {
|
||||
if (!response) return
|
||||
|
||||
if (response.data.success === false) {
|
||||
const failed = Array.isArray(response.data.failed)
|
||||
? response.data.failed
|
||||
: []
|
||||
|
||||
if (response.data.description === 'No token provided')
|
||||
return page.verifyToken(page.token)
|
||||
else if (failed.length)
|
||||
return swal('An error occurred!', `Unable to delete ${failed.length} of the user's upload${failed.length === 1 ? '' : 's'}.`, 'error')
|
||||
else
|
||||
return swal('An error occurred!', response.data.description, 'error')
|
||||
}
|
||||
|
||||
swal('Success!', `${page.cache.users[id].username} has been deleted.`, 'success')
|
||||
|
||||
// Reload users list
|
||||
// eslint-disable-next-line compat/compat
|
||||
page.getUsers(Object.assign({
|
||||
autoPage: true
|
||||
}, page.views.users))
|
||||
}).catch(page.onAxiosError)
|
||||
})
|
||||
}
|
||||
|
||||
// Roughly based on https://github.com/mayuska/pagination/blob/master/index.js
|
||||
page.paginate = (totalItems, itemsPerPage, currentPage) => {
|
||||
currentPage = currentPage + 1
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"1": "1570315036",
|
||||
"1": "1570403431",
|
||||
"2": "1568894058",
|
||||
"3": "1568894058",
|
||||
"4": "1568894058",
|
||||
|
Loading…
Reference in New Issue
Block a user