mirror of
https://github.com/BobbyWibowo/lolisafe.git
synced 2024-12-12 23:46:22 +00:00
Updated
Improved performance of /api/users/:id (admin's manage users).
Promisify fs.writeFile.
Improved performance of /api/stats.
By a lot in Linux, cause uploads size will be deferred to "du" binary.
In addition, total usage of whichever disk uploads path resides on will
also be queried using "df" binary.
Non-Linux will have to rely on manual calculation by querying DB
for each upload's size.
But logics related to uploads stats were also improved to be almost
twice as fast as before.
Improved parsing of /api/stats results on dashboard.js.
This allows ease of extending server's response by not having to update
dashboard.js by much, if at all.
Improved codes relating to item menus in dashboard's sidebar.
Finally much cleaner now 👍
No longer use /api/upload/delete API route from dashboard.
Single file deletion and bulk files deletion, both from uploads list or
by names, will now properly use a single function that will use
/api/upload/bulkdelete API route.
/api/upload/delete will still be kept indefinitely for backward support.
Fixed oddities with Select all checkbox.
Replaced all instances of modifying HTML element's style attribute with
adding/removing is-hidden CSS helper class.
Rephrased all instances of "files" to "uploads" in any display strings.
Fixed notice message when server is on private mode.
A few other improvements.
This commit is contained in:
parent
df8cac0f0b
commit
264bd88e88
@ -205,33 +205,22 @@ self.listUsers = async (req, res, next) => {
|
||||
.offset(25 * offset)
|
||||
.select('id', 'username', 'enabled', 'permission')
|
||||
|
||||
const userids = []
|
||||
|
||||
const pointers = {}
|
||||
for (const user of users) {
|
||||
user.groups = perms.mapPermissions(user)
|
||||
delete user.permission
|
||||
|
||||
userids.push(user.id)
|
||||
user.uploadsCount = 0
|
||||
user.diskUsage = 0
|
||||
user.uploads = 0
|
||||
user.usage = 0
|
||||
pointers[user.id] = user
|
||||
}
|
||||
|
||||
const maps = {}
|
||||
const uploads = await db.table('files')
|
||||
.whereIn('userid', userids)
|
||||
.whereIn('userid', Object.keys(pointers))
|
||||
.select('userid', 'size')
|
||||
|
||||
for (const upload of uploads) {
|
||||
if (maps[upload.userid] === undefined)
|
||||
maps[upload.userid] = { count: 0, size: 0 }
|
||||
|
||||
maps[upload.userid].count++
|
||||
maps[upload.userid].size += parseInt(upload.size)
|
||||
}
|
||||
|
||||
for (const user of users) {
|
||||
if (!maps[user.id]) continue
|
||||
user.uploadsCount = maps[user.id].count
|
||||
user.diskUsage = maps[user.id].size
|
||||
pointers[upload.userid].uploads++
|
||||
pointers[upload.userid].usage += parseInt(upload.size)
|
||||
}
|
||||
|
||||
return res.json({ success: true, users, count })
|
||||
|
@ -16,7 +16,8 @@ const fsFuncs = [
|
||||
'rename',
|
||||
'rmdir',
|
||||
'symlink',
|
||||
'unlink'
|
||||
'unlink',
|
||||
'writeFile'
|
||||
]
|
||||
|
||||
for (const fsFunc of fsFuncs)
|
||||
|
@ -312,12 +312,7 @@ self.actuallyUploadUrls = async (req, res, user, albumid, age) => {
|
||||
const name = await self.getUniqueRandomName(length, extname)
|
||||
|
||||
const destination = path.join(paths.uploads, name)
|
||||
await new Promise((resolve, reject) => {
|
||||
fs.writeFile(destination, file, error => {
|
||||
if (error) return reject(error)
|
||||
return resolve()
|
||||
})
|
||||
})
|
||||
await paths.writeFile(destination, file)
|
||||
downloaded.push(destination)
|
||||
|
||||
infoMap.push({
|
||||
|
@ -1,4 +1,5 @@
|
||||
const { promisify } = require('util')
|
||||
const { spawn } = require('child_process')
|
||||
const config = require('./../config')
|
||||
const db = require('knex')(config.database)
|
||||
const fetch = require('node-fetch')
|
||||
@ -33,6 +34,10 @@ const statsCache = {
|
||||
cache: null,
|
||||
generating: false
|
||||
},
|
||||
disk: {
|
||||
cache: null,
|
||||
generating: false
|
||||
},
|
||||
albums: {
|
||||
cache: null,
|
||||
generating: false,
|
||||
@ -500,153 +505,287 @@ self.stats = async (req, res, next) => {
|
||||
const isadmin = perms.is(user, 'admin')
|
||||
if (!isadmin) return res.status(403).end()
|
||||
|
||||
const stats = {}
|
||||
|
||||
// Re-use caches as long as they are still valid
|
||||
|
||||
if (!statsCache.system.cache && statsCache.system.generating) {
|
||||
stats.system = false
|
||||
} else if (statsCache.system.generating) {
|
||||
stats.system = statsCache.system.cache
|
||||
} else {
|
||||
statsCache.system.generating = true
|
||||
|
||||
try {
|
||||
const stats = {}
|
||||
const os = await si.osInfo()
|
||||
const currentLoad = await si.currentLoad()
|
||||
const mem = await si.mem()
|
||||
|
||||
stats.system = {
|
||||
platform: `${os.platform} ${os.arch}`,
|
||||
distro: `${os.distro} ${os.release}`,
|
||||
kernel: os.kernel,
|
||||
cpuLoad: `${currentLoad.currentload.toFixed(1)}%`,
|
||||
cpusLoad: currentLoad.cpus.map(cpu => `${cpu.load.toFixed(1)}%`).join(', '),
|
||||
systemMemory: {
|
||||
used: mem.active,
|
||||
total: mem.total
|
||||
},
|
||||
memoryUsage: process.memoryUsage().rss,
|
||||
nodeVersion: `${process.versions.node}`
|
||||
// System info
|
||||
if (!statsCache.system.cache && statsCache.system.generating) {
|
||||
stats.system = false
|
||||
} else if (statsCache.system.generating) {
|
||||
stats.system = statsCache.system.cache
|
||||
} else {
|
||||
statsCache.system.generating = true
|
||||
|
||||
const currentLoad = await si.currentLoad()
|
||||
const mem = await si.mem()
|
||||
|
||||
stats.system = {
|
||||
_types: {
|
||||
byte: ['memoryUsage'],
|
||||
byteUsage: ['systemMemory']
|
||||
},
|
||||
platform: `${os.platform} ${os.arch}`,
|
||||
distro: `${os.distro} ${os.release}`,
|
||||
kernel: os.kernel,
|
||||
cpuLoad: `${currentLoad.currentload.toFixed(1)}%`,
|
||||
cpusLoad: currentLoad.cpus.map(cpu => `${cpu.load.toFixed(1)}%`).join(', '),
|
||||
systemMemory: {
|
||||
used: mem.active,
|
||||
total: mem.total
|
||||
},
|
||||
memoryUsage: process.memoryUsage().rss,
|
||||
nodeVersion: `${process.versions.node}`
|
||||
}
|
||||
|
||||
// Update cache
|
||||
statsCache.system.cache = stats.system
|
||||
statsCache.system.generating = false
|
||||
}
|
||||
|
||||
// Update cache
|
||||
statsCache.system.cache = stats.system
|
||||
statsCache.system.generating = false
|
||||
}
|
||||
// Disk usage, only for Linux platform
|
||||
if (os.platform === 'linux')
|
||||
if (!statsCache.disk.cache && statsCache.disk.generating) {
|
||||
stats.disk = false
|
||||
} else if (statsCache.disk.generating) {
|
||||
stats.disk = statsCache.disk.cache
|
||||
} else {
|
||||
statsCache.disk.generating = true
|
||||
|
||||
if (!statsCache.albums.cache && statsCache.albums.generating) {
|
||||
stats.albums = false
|
||||
} else if ((statsCache.albums.invalidatedAt < statsCache.albums.generatedAt) || statsCache.albums.generating) {
|
||||
stats.albums = statsCache.albums.cache
|
||||
} else {
|
||||
statsCache.albums.generating = true
|
||||
stats.albums = {
|
||||
total: 0,
|
||||
active: 0,
|
||||
downloadable: 0,
|
||||
public: 0,
|
||||
zips: 0
|
||||
// We pre-assign the keys below to guarantee their order
|
||||
stats.disk = {
|
||||
_types: {
|
||||
byte: ['uploads', 'thumbs', 'zips', 'chunks'],
|
||||
byteUsage: ['drive']
|
||||
},
|
||||
drive: null,
|
||||
uploads: 0,
|
||||
thumbs: 0,
|
||||
zips: 0,
|
||||
chunks: 0
|
||||
}
|
||||
|
||||
// Get size of directories in uploads path
|
||||
await new Promise((resolve, reject) => {
|
||||
const proc = spawn('du', [
|
||||
'--apparent-size',
|
||||
'--block-size=1',
|
||||
'--dereference',
|
||||
'--separate-dirs',
|
||||
paths.uploads
|
||||
])
|
||||
|
||||
proc.stdout.on('data', data => {
|
||||
const formatted = String(data)
|
||||
.trim()
|
||||
.split(/\s+/)
|
||||
if (formatted.length !== 2) return
|
||||
|
||||
const basename = path.basename(formatted[1])
|
||||
stats.disk[basename] = parseInt(formatted[0])
|
||||
|
||||
// Add to types if necessary
|
||||
if (!stats.disk._types.byte.includes(basename))
|
||||
stats.disk._types.byte.push(basename)
|
||||
})
|
||||
|
||||
const stderr = []
|
||||
proc.stderr.on('data', data => stderr.push(data))
|
||||
|
||||
proc.on('exit', code => {
|
||||
if (code !== 0) return reject(stderr)
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
|
||||
// Get disk usage of whichever disk uploads path resides on
|
||||
await new Promise((resolve, reject) => {
|
||||
const proc = spawn('df', [
|
||||
'--block-size=1',
|
||||
'--output=used,size',
|
||||
paths.uploads
|
||||
])
|
||||
|
||||
proc.stdout.on('data', data => {
|
||||
// Only use the first valid line
|
||||
if (stats.disk.drive !== null) return
|
||||
|
||||
const lines = String(data)
|
||||
.trim()
|
||||
.split('\n')
|
||||
if (lines.length !== 2) return
|
||||
|
||||
for (const line of lines) {
|
||||
const columns = line.split(/\s+/)
|
||||
// Skip lines that have non-number chars
|
||||
if (columns.some(w => !/^\d+$/.test(w))) continue
|
||||
|
||||
stats.disk.drive = {
|
||||
used: parseInt(columns[0]),
|
||||
total: parseInt(columns[1])
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const stderr = []
|
||||
proc.stderr.on('data', data => stderr.push(data))
|
||||
|
||||
proc.on('exit', code => {
|
||||
if (code !== 0) return reject(stderr)
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
|
||||
// Update cache
|
||||
statsCache.disk.cache = stats.system
|
||||
statsCache.disk.generating = false
|
||||
}
|
||||
|
||||
// Uploads
|
||||
if (!statsCache.uploads.cache && statsCache.uploads.generating) {
|
||||
stats.uploads = false
|
||||
} else if ((statsCache.uploads.invalidatedAt < statsCache.uploads.generatedAt) || statsCache.uploads.generating) {
|
||||
stats.uploads = statsCache.uploads.cache
|
||||
} else {
|
||||
statsCache.uploads.generating = true
|
||||
stats.uploads = {
|
||||
_types: {
|
||||
number: ['total', 'images', 'videos', 'others']
|
||||
},
|
||||
total: 0,
|
||||
images: 0,
|
||||
videos: 0,
|
||||
others: 0
|
||||
}
|
||||
|
||||
if (os.platform !== 'linux') {
|
||||
// If not Linux platform, rely on DB for total size
|
||||
const uploads = await db.table('files')
|
||||
.select('size')
|
||||
stats.uploads.total = uploads.length
|
||||
stats.uploads.sizeInDb = uploads.reduce((acc, upload) => acc + parseInt(upload.size), 0)
|
||||
// Add type information for the new column
|
||||
if (!Array.isArray(stats.uploads._types.byte))
|
||||
stats.uploads._types.byte = []
|
||||
stats.uploads._types.byte.push('sizeInDb')
|
||||
} else {
|
||||
stats.uploads.total = await db.table('files')
|
||||
.count('id as count')
|
||||
.then(rows => rows[0].count)
|
||||
}
|
||||
|
||||
stats.uploads.images = await db.table('files')
|
||||
.whereRaw(self.imageExts.map(ext => `\`name\` like '%${ext}'`).join(' or '))
|
||||
.count('id as count')
|
||||
.then(rows => rows[0].count)
|
||||
|
||||
stats.uploads.videos = await db.table('files')
|
||||
.whereRaw(self.videoExts.map(ext => `\`name\` like '%${ext}'`).join(' or '))
|
||||
.count('id as count')
|
||||
.then(rows => rows[0].count)
|
||||
|
||||
stats.uploads.others = stats.uploads.total - stats.uploads.images - stats.uploads.videos
|
||||
|
||||
// Update cache
|
||||
statsCache.uploads.cache = stats.uploads
|
||||
statsCache.uploads.generatedAt = Date.now()
|
||||
statsCache.uploads.generating = false
|
||||
}
|
||||
|
||||
const albums = await db.table('albums')
|
||||
stats.albums.total = albums.length
|
||||
const identifiers = []
|
||||
for (const album of albums)
|
||||
if (album.enabled) {
|
||||
stats.albums.active++
|
||||
// Users
|
||||
if (!statsCache.users.cache && statsCache.users.generating) {
|
||||
stats.users = false
|
||||
} else if ((statsCache.users.invalidatedAt < statsCache.users.generatedAt) || statsCache.users.generating) {
|
||||
stats.users = statsCache.users.cache
|
||||
} else {
|
||||
statsCache.users.generating = true
|
||||
stats.users = {
|
||||
_types: {
|
||||
number: ['total', 'disabled']
|
||||
},
|
||||
total: 0,
|
||||
disabled: 0
|
||||
}
|
||||
|
||||
const permissionKeys = Object.keys(perms.permissions).reverse()
|
||||
permissionKeys.forEach(p => {
|
||||
stats.users[p] = 0
|
||||
stats.users._types.number.push(p)
|
||||
})
|
||||
|
||||
const users = await db.table('users')
|
||||
stats.users.total = users.length
|
||||
for (const user of users) {
|
||||
if (user.enabled === false || user.enabled === 0)
|
||||
stats.users.disabled++
|
||||
|
||||
// This may be inaccurate on installations with customized permissions
|
||||
user.permission = user.permission || 0
|
||||
for (const p of permissionKeys)
|
||||
if (user.permission === perms.permissions[p]) {
|
||||
stats.users[p]++
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Update cache
|
||||
statsCache.users.cache = stats.users
|
||||
statsCache.users.generatedAt = Date.now()
|
||||
statsCache.users.generating = false
|
||||
}
|
||||
|
||||
// Albums
|
||||
if (!statsCache.albums.cache && statsCache.albums.generating) {
|
||||
stats.albums = false
|
||||
} else if ((statsCache.albums.invalidatedAt < statsCache.albums.generatedAt) || statsCache.albums.generating) {
|
||||
stats.albums = statsCache.albums.cache
|
||||
} else {
|
||||
statsCache.albums.generating = true
|
||||
stats.albums = {
|
||||
_types: {
|
||||
number: ['total', 'active', 'downloadable', 'public', 'generatedZip']
|
||||
},
|
||||
total: 0,
|
||||
disabled: 0,
|
||||
public: 0,
|
||||
downloadable: 0,
|
||||
zipGenerated: 0
|
||||
}
|
||||
|
||||
const albums = await db.table('albums')
|
||||
stats.albums.total = albums.length
|
||||
const identifiers = []
|
||||
for (const album of albums) {
|
||||
if (!album.enabled) {
|
||||
stats.albums.disabled++
|
||||
continue
|
||||
}
|
||||
if (album.download) stats.albums.downloadable++
|
||||
if (album.public) stats.albums.public++
|
||||
if (album.zipGeneratedAt) identifiers.push(album.identifier)
|
||||
}
|
||||
|
||||
const zipsDir = path.join(paths.uploads, 'zips')
|
||||
await Promise.all(identifiers.map(identifier => {
|
||||
return new Promise(resolve => {
|
||||
const filePath = path.join(zipsDir, `${identifier}.zip`)
|
||||
fs.access(filePath, error => {
|
||||
if (!error) stats.albums.zips++
|
||||
resolve(true)
|
||||
})
|
||||
})
|
||||
}))
|
||||
|
||||
// Update cache
|
||||
statsCache.albums.cache = stats.albums
|
||||
statsCache.albums.generatedAt = Date.now()
|
||||
statsCache.albums.generating = false
|
||||
}
|
||||
|
||||
if (!statsCache.users.cache && statsCache.users.generating) {
|
||||
stats.users = false
|
||||
} else if ((statsCache.users.invalidatedAt < statsCache.users.generatedAt) || statsCache.users.generating) {
|
||||
stats.users = statsCache.users.cache
|
||||
} else {
|
||||
statsCache.users.generating = true
|
||||
stats.users = {
|
||||
total: 0,
|
||||
disabled: 0
|
||||
}
|
||||
|
||||
const permissionKeys = Object.keys(perms.permissions)
|
||||
permissionKeys.forEach(p => {
|
||||
stats.users[p] = 0
|
||||
})
|
||||
|
||||
const users = await db.table('users')
|
||||
stats.users.total = users.length
|
||||
for (const user of users) {
|
||||
if (user.enabled === false || user.enabled === 0)
|
||||
stats.users.disabled++
|
||||
|
||||
// This may be inaccurate on installations with customized permissions
|
||||
user.permission = user.permission || 0
|
||||
for (const p of permissionKeys)
|
||||
if (user.permission === perms.permissions[p]) {
|
||||
stats.users[p]++
|
||||
break
|
||||
for (const identifier of identifiers)
|
||||
try {
|
||||
await paths.access(path.join(paths.zips, `${identifier}.zip`))
|
||||
stats.albums.zipGenerated++
|
||||
} catch (error) {
|
||||
// Re-throw error
|
||||
if (error.code !== 'ENOENT')
|
||||
throw error
|
||||
}
|
||||
|
||||
// Update cache
|
||||
statsCache.albums.cache = stats.albums
|
||||
statsCache.albums.generatedAt = Date.now()
|
||||
statsCache.albums.generating = false
|
||||
}
|
||||
|
||||
// Update cache
|
||||
statsCache.users.cache = stats.users
|
||||
statsCache.users.generatedAt = Date.now()
|
||||
statsCache.users.generating = false
|
||||
return res.json({ success: true, stats })
|
||||
} catch (error) {
|
||||
logger.error(error)
|
||||
return res.status(500).json({ success: false, description: 'An unexpected error occurred. Try again?' })
|
||||
}
|
||||
|
||||
if (!statsCache.uploads.cache && statsCache.uploads.generating) {
|
||||
stats.uploads = false
|
||||
} else if ((statsCache.uploads.invalidatedAt < statsCache.uploads.generatedAt) || statsCache.uploads.generating) {
|
||||
stats.uploads = statsCache.uploads.cache
|
||||
} else {
|
||||
statsCache.uploads.generating = true
|
||||
stats.uploads = {
|
||||
total: 0,
|
||||
size: 0,
|
||||
images: 0,
|
||||
videos: 0,
|
||||
others: 0
|
||||
}
|
||||
|
||||
const uploads = await db.table('files')
|
||||
stats.uploads.total = uploads.length
|
||||
for (const upload of uploads) {
|
||||
stats.uploads.size += parseInt(upload.size)
|
||||
const extname = self.extname(upload.name)
|
||||
if (self.imageExts.includes(extname))
|
||||
stats.uploads.images++
|
||||
else if (self.videoExts.includes(extname))
|
||||
stats.uploads.videos++
|
||||
else
|
||||
stats.uploads.others++
|
||||
}
|
||||
|
||||
// Update cache
|
||||
statsCache.uploads.cache = stats.uploads
|
||||
statsCache.uploads.generatedAt = Date.now()
|
||||
statsCache.uploads.generating = false
|
||||
}
|
||||
|
||||
return res.json({ success: true, stats })
|
||||
}
|
||||
|
||||
module.exports = self
|
||||
|
@ -3,9 +3,7 @@ body {
|
||||
animation: none;
|
||||
}
|
||||
|
||||
#auth,
|
||||
#dashboard {
|
||||
display: none;
|
||||
-webkit-animation: fadeInOpacity .5s;
|
||||
animation: fadeInOpacity .5s;
|
||||
}
|
||||
@ -102,15 +100,11 @@ li[data-action="page-ellipsis"] {
|
||||
|
||||
.no-touch .image-container .checkbox {
|
||||
opacity: .5;
|
||||
-webkit-transition: opacity .25s;
|
||||
transition: opacity .25s;
|
||||
}
|
||||
|
||||
.no-touch .image-container .controls,
|
||||
.no-touch .image-container .details {
|
||||
opacity: 0;
|
||||
-webkit-transition: opacity .25s;
|
||||
transition: opacity .25s;
|
||||
}
|
||||
|
||||
.no-touch .image-container:hover .checkbox,
|
||||
|
@ -24,6 +24,9 @@ const page = {
|
||||
username: null,
|
||||
permissions: null,
|
||||
|
||||
// sidebar menus
|
||||
menus: [],
|
||||
|
||||
currentView: null,
|
||||
views: {
|
||||
// config of uploads view
|
||||
@ -75,7 +78,8 @@ const page = {
|
||||
clipboardJS: null,
|
||||
lazyLoad: null,
|
||||
|
||||
imageExtensions: ['.webp', '.jpg', '.jpeg', '.bmp', '.gif', '.png'],
|
||||
imageExts: ['.webp', '.jpg', '.jpeg', '.gif', '.png', '.tiff', '.tif', '.svg'],
|
||||
videoExts: ['.webm', '.mp4', '.wmv', '.avi', '.mov', '.mkv'],
|
||||
|
||||
fadingIn: null
|
||||
}
|
||||
@ -115,77 +119,64 @@ page.verifyToken = function (token, reloadOnError) {
|
||||
|
||||
page.prepareDashboard = function () {
|
||||
page.dom = document.querySelector('#page')
|
||||
|
||||
// Capture all click events
|
||||
page.dom.addEventListener('click', page.domClick, true)
|
||||
|
||||
// Capture all submit events
|
||||
page.dom.addEventListener('submit', function (event) {
|
||||
const element = event.target
|
||||
if (element && element.classList.contains('prevent-default'))
|
||||
// Prevent default if necessary
|
||||
if (event.target && event.target.classList.contains('prevent-default'))
|
||||
return event.preventDefault()
|
||||
}, true)
|
||||
|
||||
document.querySelector('#dashboard').style.display = 'block'
|
||||
// All item menus in the sidebar
|
||||
const itemMenus = [
|
||||
{ selector: '#itemUploads', onclick: page.getUploads },
|
||||
{ selector: '#itemDeleteUploadsByNames', onclick: page.deleteUploadsByNames },
|
||||
{ selector: '#itemManageAlbums', onclick: page.getAlbums },
|
||||
{ selector: '#itemManageToken', onclick: page.changeToken },
|
||||
{ selector: '#itemChangePassword', onclick: page.changePassword },
|
||||
{ selector: '#itemLogout', onclick: page.logout, inactive: true },
|
||||
{ selector: '#itemManageUploads', onclick: page.getUploads, params: [{ all: true }], group: 'moderator' },
|
||||
{ selector: '#itemStatistics', onclick: page.getStatistics, group: 'admin' },
|
||||
{ selector: '#itemManageUsers', onclick: page.getUsers, group: 'admin' }
|
||||
]
|
||||
|
||||
for (let i = 0; i < itemMenus.length; i++) {
|
||||
// Skip item menu if not enough permission
|
||||
if (itemMenus[i].group && !page.permissions[itemMenus[i].group])
|
||||
continue
|
||||
|
||||
// Add onclick event listener
|
||||
const item = document.querySelector(itemMenus[i].selector)
|
||||
item.addEventListener('click', function () {
|
||||
itemMenus[i].onclick.apply(null, itemMenus[i].params)
|
||||
if (!itemMenus[i].inactive)
|
||||
page.setActiveMenu(this)
|
||||
})
|
||||
|
||||
item.classList.remove('is-hidden')
|
||||
page.menus.push(item)
|
||||
}
|
||||
|
||||
// If at least a moderator, show administration section
|
||||
if (page.permissions.moderator) {
|
||||
document.querySelector('#itemLabelAdmin').style.display = 'block'
|
||||
document.querySelector('#itemListAdmin').style.display = 'block'
|
||||
const itemManageUploads = document.querySelector('#itemManageUploads')
|
||||
itemManageUploads.addEventListener('click', function () {
|
||||
page.setActiveMenu(this)
|
||||
page.getUploads({ all: true })
|
||||
})
|
||||
document.querySelector('#itemLabelAdmin').classList.remove('is-hidden')
|
||||
document.querySelector('#itemListAdmin').classList.remove('is-hidden')
|
||||
}
|
||||
|
||||
if (page.permissions.admin) {
|
||||
const itemServerStats = document.querySelector('#itemServerStats')
|
||||
itemServerStats.addEventListener('click', function () {
|
||||
page.setActiveMenu(this)
|
||||
page.getServerStats()
|
||||
})
|
||||
// Update text of logout button
|
||||
document.querySelector('#itemLogout').innerHTML = `Logout ( ${page.username} )`
|
||||
|
||||
const itemManageUsers = document.querySelector('#itemManageUsers')
|
||||
itemManageUsers.addEventListener('click', function () {
|
||||
page.setActiveMenu(this)
|
||||
page.getUsers()
|
||||
})
|
||||
} else {
|
||||
document.querySelector('#itemServerStats').style.display = 'none'
|
||||
document.querySelector('#itemManageUsers').style.display = 'none'
|
||||
}
|
||||
|
||||
document.querySelector('#itemUploads').addEventListener('click', function () {
|
||||
page.setActiveMenu(this)
|
||||
page.getUploads({ all: false })
|
||||
})
|
||||
|
||||
document.querySelector('#itemDeleteByNames').addEventListener('click', function () {
|
||||
page.setActiveMenu(this)
|
||||
page.deleteByNames()
|
||||
})
|
||||
|
||||
document.querySelector('#itemManageGallery').addEventListener('click', function () {
|
||||
page.setActiveMenu(this)
|
||||
page.getAlbums()
|
||||
})
|
||||
|
||||
document.querySelector('#itemTokens').addEventListener('click', function () {
|
||||
page.setActiveMenu(this)
|
||||
page.changeToken()
|
||||
})
|
||||
|
||||
document.querySelector('#itemPassword').addEventListener('click', function () {
|
||||
page.setActiveMenu(this)
|
||||
page.changePassword()
|
||||
})
|
||||
|
||||
const logoutBtn = document.querySelector('#itemLogout')
|
||||
logoutBtn.addEventListener('click', function () {
|
||||
page.logout()
|
||||
})
|
||||
logoutBtn.innerHTML = `Logout ( ${page.username} )`
|
||||
// Finally display dashboard
|
||||
document.querySelector('#dashboard').classList.remove('is-hidden')
|
||||
|
||||
// Load albums sidebar
|
||||
page.getAlbumsSidebar()
|
||||
|
||||
if (typeof page.prepareShareX === 'function') page.prepareShareX()
|
||||
if (typeof page.prepareShareX === 'function')
|
||||
page.prepareShareX()
|
||||
}
|
||||
|
||||
page.logout = function () {
|
||||
@ -234,22 +225,20 @@ page.domClick = function (event) {
|
||||
return page.setUploadsView('thumbs', element)
|
||||
case 'clear-selection':
|
||||
return page.clearSelection()
|
||||
case 'add-selected-files-to-album':
|
||||
return page.addSelectedFilesToAlbum()
|
||||
case 'bulk-delete':
|
||||
return page.deleteSelectedFiles()
|
||||
case 'add-selected-uploads-to-album':
|
||||
return page.addSelectedUploadsToAlbum()
|
||||
case 'select':
|
||||
return page.select(element, event)
|
||||
case 'add-to-album':
|
||||
return page.addSingleFileToAlbum(id)
|
||||
case 'delete-file':
|
||||
return page.deleteFile(id)
|
||||
case 'select-all':
|
||||
return page.selectAll(element)
|
||||
case 'add-to-album':
|
||||
return page.addToAlbum(id)
|
||||
case 'delete-upload':
|
||||
return page.deleteUpload(id)
|
||||
case 'bulk-delete-uploads':
|
||||
return page.bulkDeleteUploads()
|
||||
case 'display-thumbnail':
|
||||
return page.displayThumbnail(id)
|
||||
case 'delete-file-by-names':
|
||||
return page.deleteFileByNames()
|
||||
case 'submit-album':
|
||||
return page.submitAlbum(element)
|
||||
case 'edit-album':
|
||||
@ -457,12 +446,12 @@ page.getUploads = function ({ pageNum, album, all, filters, autoPage } = {}, ele
|
||||
</span>
|
||||
</a>
|
||||
${all ? '' : `
|
||||
<a class="button is-small is-warning" title="Add selected uploads to album" data-action="add-selected-files-to-album">
|
||||
<a class="button is-small is-warning" title="Bulk add to album" data-action="add-selected-uploads-to-album">
|
||||
<span class="icon">
|
||||
<i class="icon-plus"></i>
|
||||
</span>
|
||||
</a>`}
|
||||
<a class="button is-small is-danger" title="Bulk delete" data-action="bulk-delete">
|
||||
<a class="button is-small is-danger" title="Bulk delete" data-action="bulk-delete-uploads">
|
||||
<span class="icon">
|
||||
<i class="icon-trash"></i>
|
||||
</span>
|
||||
@ -472,8 +461,8 @@ page.getUploads = function ({ pageNum, album, all, filters, autoPage } = {}, ele
|
||||
</div>
|
||||
`
|
||||
|
||||
// Set to true to tick "all files" checkbox in list view
|
||||
let allSelected = true
|
||||
// Whether there are any unselected items
|
||||
let unselected = false
|
||||
|
||||
const hasExpiryDateColumn = files.some(file => file.expirydate !== undefined)
|
||||
|
||||
@ -501,7 +490,7 @@ page.getUploads = function ({ pageNum, album, all, filters, autoPage } = {}, ele
|
||||
|
||||
// Update selected status
|
||||
files[i].selected = page.selected[page.currentView].includes(files[i].id)
|
||||
if (allSelected && !files[i].selected) allSelected = false
|
||||
if (!files[i].selected) unselected = true
|
||||
|
||||
// Appendix (display album or user)
|
||||
if (all)
|
||||
@ -539,7 +528,7 @@ page.getUploads = function ({ pageNum, album, all, filters, autoPage } = {}, ele
|
||||
div.innerHTML = `<a class="image" href="${upload.file}" target="_blank" rel="noopener"><h1 class="title">${upload.extname || 'N/A'}</h1></a>`
|
||||
|
||||
div.innerHTML += `
|
||||
<input type="checkbox" class="checkbox" title="Select this file" data-action="select"${upload.selected ? ' checked' : ''}>
|
||||
<input type="checkbox" class="checkbox" title="Select" data-action="select"${upload.selected ? ' checked' : ''}>
|
||||
<div class="controls">
|
||||
<a class="button is-small is-primary" title="View thumbnail" data-action="display-thumbnail"${upload.thumb ? '' : ' disabled'}>
|
||||
<span class="icon">
|
||||
@ -556,7 +545,7 @@ page.getUploads = function ({ pageNum, album, all, filters, autoPage } = {}, ele
|
||||
<i class="icon-plus"></i>
|
||||
</span>
|
||||
</a>
|
||||
<a class="button is-small is-danger" title="Delete file" data-action="delete-file">
|
||||
<a class="button is-small is-danger" title="Delete" data-action="delete-upload">
|
||||
<span class="icon">
|
||||
<i class="icon-trash"></i>
|
||||
</span>
|
||||
@ -581,7 +570,7 @@ page.getUploads = function ({ pageNum, album, all, filters, autoPage } = {}, ele
|
||||
<table class="table is-narrow is-fullwidth is-hoverable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><input id="selectAll" class="checkbox" type="checkbox" title="Select all uploads" data-action="select-all"></th>
|
||||
<th><input id="selectAll" class="checkbox" type="checkbox" title="Select all" data-action="select-all"></th>
|
||||
<th style="width: 20%">File</th>
|
||||
<th>${all ? 'User' : 'Album'}</th>
|
||||
<th>Size</th>
|
||||
@ -606,7 +595,7 @@ page.getUploads = function ({ pageNum, album, all, filters, autoPage } = {}, ele
|
||||
const tr = document.createElement('tr')
|
||||
tr.dataset.id = upload.id
|
||||
tr.innerHTML = `
|
||||
<td class="controls"><input type="checkbox" class="checkbox" title="Select this file" data-action="select"${upload.selected ? ' checked' : ''}></td>
|
||||
<td class="controls"><input type="checkbox" class="checkbox" title="Select" data-action="select"${upload.selected ? ' checked' : ''}></td>
|
||||
<th><a href="${upload.file}" target="_blank" rel="noopener" title="${upload.file}">${upload.name}</a></th>
|
||||
<th>${upload.appendix}</th>
|
||||
<td>${upload.prettyBytes}</td>
|
||||
@ -630,7 +619,7 @@ page.getUploads = function ({ pageNum, album, all, filters, autoPage } = {}, ele
|
||||
<i class="icon-plus"></i>
|
||||
</span>
|
||||
</a>`}
|
||||
<a class="button is-small is-danger" title="Delete file" data-action="delete-file">
|
||||
<a class="button is-small is-danger" title="Delete" data-action="delete-upload">
|
||||
<span class="icon">
|
||||
<i class="icon-trash"></i>
|
||||
</span>
|
||||
@ -642,13 +631,15 @@ page.getUploads = function ({ pageNum, album, all, filters, autoPage } = {}, ele
|
||||
page.checkboxes[page.currentView] = Array.from(table.querySelectorAll('.checkbox[data-action="select"]'))
|
||||
}
|
||||
}
|
||||
page.fadeAndScroll()
|
||||
|
||||
if (allSelected && files.length) {
|
||||
const selectAll = document.querySelector('#selectAll')
|
||||
if (selectAll) selectAll.checked = true
|
||||
const selectAll = document.querySelector('#selectAll')
|
||||
if (selectAll && !unselected) {
|
||||
selectAll.checked = true
|
||||
selectAll.title = 'Unselect all'
|
||||
}
|
||||
|
||||
page.fadeAndScroll()
|
||||
|
||||
if (page.currentView === 'uploads') page.views.uploads.album = album
|
||||
if (page.currentView === 'uploadsAll') page.views.uploadsAll.filters = filters
|
||||
page.views[page.currentView].pageNum = files.length ? pageNum : 0
|
||||
@ -693,27 +684,28 @@ page.displayThumbnail = function (id) {
|
||||
|
||||
const thumb = div.querySelector('#swalThumb')
|
||||
const exec = /.[\w]+(\?|$)/.exec(original)
|
||||
const isimage = exec && exec[0] && page.imageExtensions.includes(exec[0].toLowerCase())
|
||||
if (!exec || !exec[0]) return
|
||||
|
||||
if (isimage) {
|
||||
const extname = exec[0].toLowerCase()
|
||||
if (page.imageExts.includes(extname)) {
|
||||
thumb.src = file.original
|
||||
thumb.onload = function () {
|
||||
button.style.display = 'none'
|
||||
button.classList.add('is-hidden')
|
||||
document.body.querySelector('.swal-overlay .swal-modal:not(.is-expanded)').classList.add('is-expanded')
|
||||
}
|
||||
thumb.onerror = function () {
|
||||
button.className = 'button is-danger'
|
||||
button.innerHTML = 'Unable to load original'
|
||||
}
|
||||
} else {
|
||||
thumb.style.display = 'none'
|
||||
} else if (page.videoExts.includes(extname)) {
|
||||
thumb.classList.add('is-hidden')
|
||||
const video = document.createElement('video')
|
||||
video.id = 'swalVideo'
|
||||
video.controls = true
|
||||
video.src = file.original
|
||||
thumb.insertAdjacentElement('afterend', video)
|
||||
|
||||
button.style.display = 'none'
|
||||
button.classList.add('is-hidden')
|
||||
document.body.querySelector('.swal-overlay .swal-modal:not(.is-expanded)').classList.add('is-expanded')
|
||||
}
|
||||
})
|
||||
@ -732,68 +724,76 @@ page.displayThumbnail = function (id) {
|
||||
}
|
||||
|
||||
page.selectAll = function (element) {
|
||||
const checkboxes = page.checkboxes[page.currentView]
|
||||
const selected = page.selected[page.currentView]
|
||||
|
||||
for (let i = 0; i < checkboxes.length; i++) {
|
||||
const id = page.getItemID(checkboxes[i])
|
||||
for (let i = 0; i < page.checkboxes[page.currentView].length; i++) {
|
||||
const id = page.getItemID(page.checkboxes[page.currentView][i])
|
||||
if (isNaN(id)) continue
|
||||
if (checkboxes[i].checked !== element.checked) {
|
||||
checkboxes[i].checked = element.checked
|
||||
if (checkboxes[i].checked)
|
||||
selected.push(id)
|
||||
if (page.checkboxes[page.currentView][i].checked !== element.checked) {
|
||||
page.checkboxes[page.currentView][i].checked = element.checked
|
||||
if (page.checkboxes[page.currentView][i].checked)
|
||||
page.selected[page.currentView].push(id)
|
||||
else
|
||||
selected.splice(selected.indexOf(id), 1)
|
||||
page.selected[page.currentView].splice(page.selected[page.currentView].indexOf(id), 1)
|
||||
}
|
||||
}
|
||||
|
||||
localStorage[lsKeys.selected[page.currentView]] = JSON.stringify(selected)
|
||||
page.selected[page.currentView] = selected
|
||||
element.title = element.checked ? 'Unselect all uploads' : 'Select all uploads'
|
||||
if (page.selected[page.currentView].length)
|
||||
localStorage[lsKeys.selected[page.currentView]] = JSON.stringify(page.selected[page.currentView])
|
||||
else
|
||||
delete localStorage[lsKeys.selected[page.currentView]]
|
||||
|
||||
element.title = element.checked ? 'Unselect all' : 'Select all'
|
||||
}
|
||||
|
||||
page.selectInBetween = function (element, lastElement) {
|
||||
if (!element || !lastElement) return
|
||||
if (element === lastElement) return
|
||||
if (!element || !lastElement || element === lastElement)
|
||||
return
|
||||
|
||||
const checkboxes = page.checkboxes[page.currentView]
|
||||
if (!checkboxes || !checkboxes.length) return
|
||||
if (!Array.isArray(page.checkboxes[page.currentView]) || !page.checkboxes[page.currentView].length)
|
||||
return
|
||||
|
||||
const thisIndex = checkboxes.indexOf(element)
|
||||
const lastIndex = checkboxes.indexOf(lastElement)
|
||||
const thisIndex = page.checkboxes[page.currentView].indexOf(element)
|
||||
const lastIndex = page.checkboxes[page.currentView].indexOf(lastElement)
|
||||
|
||||
const distance = thisIndex - lastIndex
|
||||
if (distance >= -1 && distance <= 1) return
|
||||
if (distance >= -1 && distance <= 1)
|
||||
return
|
||||
|
||||
for (let i = 0; i < checkboxes.length; i++)
|
||||
for (let i = 0; i < page.checkboxes[page.currentView].length; i++)
|
||||
if ((thisIndex > lastIndex && i > lastIndex && i < thisIndex) ||
|
||||
(thisIndex < lastIndex && i > thisIndex && i < lastIndex)) {
|
||||
checkboxes[i].checked = true
|
||||
page.selected[page.currentView].push(page.getItemID(checkboxes[i]))
|
||||
// Check or uncheck depending on the state of the initial checkbox
|
||||
page.checkboxes[page.currentView][i].checked = lastElement.checked
|
||||
const id = page.getItemID(page.checkboxes[page.currentView][i])
|
||||
if (!page.selected[page.currentView].includes(id) && page.checkboxes[page.currentView][i].checked)
|
||||
page.selected[page.currentView].push(id)
|
||||
else if (page.selected[page.currentView].includes(id) && !page.checkboxes[page.currentView][i].checked)
|
||||
page.selected[page.currentView].splice(page.selected[page.currentView].indexOf(id), 1)
|
||||
}
|
||||
|
||||
localStorage[lsKeys.selected[page.currentView]] = JSON.stringify(page.selected[page.currentView])
|
||||
page.checkboxes[page.currentView] = checkboxes
|
||||
}
|
||||
|
||||
page.select = function (element, event) {
|
||||
const lastSelected = page.lastSelected[page.currentView]
|
||||
if (event.shiftKey && lastSelected)
|
||||
page.selectInBetween(element, lastSelected)
|
||||
else
|
||||
page.lastSelected[page.currentView] = element
|
||||
|
||||
const id = page.getItemID(element)
|
||||
if (isNaN(id)) return
|
||||
|
||||
const selected = page.selected[page.currentView]
|
||||
if (!selected.includes(id) && element.checked)
|
||||
selected.push(id)
|
||||
else if (selected.includes(id) && !element.checked)
|
||||
selected.splice(selected.indexOf(id), 1)
|
||||
const lastSelected = page.lastSelected[page.currentView]
|
||||
if (event.shiftKey && lastSelected) {
|
||||
page.selectInBetween(element, lastSelected)
|
||||
// Check or uncheck depending on the state of the initial checkbox
|
||||
element.checked = lastSelected.checked
|
||||
} else {
|
||||
page.lastSelected[page.currentView] = element
|
||||
}
|
||||
|
||||
localStorage[lsKeys.selected[page.currentView]] = JSON.stringify(selected)
|
||||
page.selected[page.currentView] = selected
|
||||
if (!page.selected[page.currentView].includes(id) && element.checked)
|
||||
page.selected[page.currentView].push(id)
|
||||
else if (page.selected[page.currentView].includes(id) && !element.checked)
|
||||
page.selected[page.currentView].splice(page.selected[page.currentView].indexOf(id), 1)
|
||||
|
||||
// Update local storage
|
||||
if (page.selected[page.currentView].length)
|
||||
localStorage[lsKeys.selected[page.currentView]] = JSON.stringify(page.selected[page.currentView])
|
||||
else
|
||||
delete localStorage[lsKeys.selected[page.currentView]]
|
||||
}
|
||||
|
||||
page.clearSelection = function () {
|
||||
@ -830,7 +830,7 @@ page.filtersHelp = function (element) {
|
||||
const content = document.createElement('div')
|
||||
content.style = 'text-align: left'
|
||||
content.innerHTML = `
|
||||
This supports 3 filter keys, namely <b>user</b> (username), <b>ip</b> and <b>name</b> (file name).
|
||||
This supports 3 filter keys, namely <b>user</b> (username), <b>ip</b> and <b>name</b> (upload name).
|
||||
Each key can be specified more than once.
|
||||
Backlashes should be used if the usernames have spaces.
|
||||
There are also 2 additional flags, namely <b>-user</b> and <b>-ip</b>, which will match uploads by non-registered users and have no IPs respectively.
|
||||
@ -847,7 +847,7 @@ page.filtersHelp = function (element) {
|
||||
Uploads from users with username either "John Doe" OR "demo":
|
||||
<code>user:John\\ Doe user:demo</code>
|
||||
|
||||
Uploads from IP "127.0.0.1" AND which file names match "*.rar" OR "*.zip":
|
||||
Uploads from IP "127.0.0.1" AND which upload names match "*.rar" OR "*.zip":
|
||||
<code>ip:127.0.0.1 name:*.rar name:*.zip</code>
|
||||
|
||||
Uploads from user with username "test" OR from non-registered users:
|
||||
@ -864,56 +864,133 @@ page.filterUploads = function (element) {
|
||||
page.viewUserUploads = function (id) {
|
||||
const user = page.cache.users[id]
|
||||
if (!user) return
|
||||
page.setActiveMenu(document.querySelector('#itemManageUploads'))
|
||||
page.getUploads({ all: true, filters: `user:${user.username.replace(/ /g, '\\ ')}` })
|
||||
page.setActiveMenu(document.querySelector('#itemManageUploads'))
|
||||
}
|
||||
|
||||
page.deleteFile = function (id) {
|
||||
// TODO: Share function with bulk delete, just like 'add selected uploads to album' and 'add single file to album'
|
||||
swal({
|
||||
title: 'Are you sure?',
|
||||
text: 'You won\'t be able to recover the file!',
|
||||
icon: 'warning',
|
||||
dangerMode: true,
|
||||
buttons: {
|
||||
cancel: true,
|
||||
confirm: {
|
||||
text: 'Yes, delete it!',
|
||||
closeModal: false
|
||||
}
|
||||
}
|
||||
}).then(function (proceed) {
|
||||
if (!proceed) return
|
||||
page.deleteUpload = function (id) {
|
||||
page.postBulkDeleteUploads({
|
||||
field: 'id',
|
||||
values: [id],
|
||||
cb (failed) {
|
||||
// Remove from remembered checkboxes if necessary
|
||||
if (!failed.length && page.selected[page.currentView].includes(id))
|
||||
page.selected[page.currentView].splice(page.selected[page.currentView].indexOf(id), 1)
|
||||
|
||||
axios.post('api/upload/delete', { id }).then(function (response) {
|
||||
if (!response) return
|
||||
|
||||
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('Deleted!', 'The file has been deleted.', 'success')
|
||||
// Update local storage
|
||||
if (page.selected[page.currentView].length)
|
||||
localStorage[lsKeys.selected[page.currentView]] = JSON.stringify(page.selected[page.currentView])
|
||||
else
|
||||
delete localStorage[lsKeys.selected[page.currentView]]
|
||||
|
||||
// Reload upload list
|
||||
const views = Object.assign({}, page.views[page.currentView])
|
||||
views.autoPage = true
|
||||
page.getUploads(views)
|
||||
}).catch(function (error) {
|
||||
console.error(error)
|
||||
return swal('An error occurred!', 'There was an error with the request, please check the console for more information.', 'error')
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
page.deleteSelectedFiles = function () {
|
||||
page.bulkDeleteUploads = function () {
|
||||
const count = page.selected[page.currentView].length
|
||||
if (!count)
|
||||
return swal('An error occurred!', 'You have not selected any uploads.', 'error')
|
||||
|
||||
const suffix = `upload${count === 1 ? '' : 's'}`
|
||||
let text = `You won't be able to recover ${count} ${suffix}!`
|
||||
page.postBulkDeleteUploads({
|
||||
field: 'id',
|
||||
values: page.selected[page.currentView],
|
||||
cb (failed) {
|
||||
// Update state of checkboxes
|
||||
if (failed.length)
|
||||
page.selected[page.currentView] = page.selected[page.currentView]
|
||||
.filter(function (id) {
|
||||
return failed.includes(id)
|
||||
})
|
||||
else
|
||||
page.selected[page.currentView] = []
|
||||
|
||||
// Update local storage
|
||||
if (page.selected[page.currentView].length)
|
||||
localStorage[lsKeys.selected[page.currentView]] = JSON.stringify(page.selected[page.currentView])
|
||||
else
|
||||
delete localStorage[lsKeys.selected[page.currentView]]
|
||||
|
||||
// Reload uploads list
|
||||
const views = Object.assign({}, page.views[page.currentView])
|
||||
views.autoPage = true
|
||||
page.getUploads(views)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
page.deleteUploadsByNames = function () {
|
||||
let appendix = ''
|
||||
if (page.permissions.moderator)
|
||||
appendix = '<br>As a staff, you can use this feature to delete uploads from other users.'
|
||||
|
||||
page.dom.innerHTML = `
|
||||
<form class="prevent-default">
|
||||
<div class="field">
|
||||
<label class="label">Upload names:</label>
|
||||
<div class="control">
|
||||
<textarea id="bulkDeleteNames" class="textarea"></textarea>
|
||||
</div>
|
||||
<p class="help">Separate each entry with a new line.${appendix}</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<button type="submit" id="submitBulkDelete" class="button is-danger is-fullwidth">
|
||||
<span class="icon">
|
||||
<i class="icon-trash"></i>
|
||||
</span>
|
||||
<span>Bulk delete</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
`
|
||||
page.fadeAndScroll()
|
||||
|
||||
document.querySelector('#submitBulkDelete').addEventListener('click', function () {
|
||||
const textArea = document.querySelector('#bulkDeleteNames')
|
||||
|
||||
// Clean up
|
||||
const seen = {}
|
||||
const names = textArea.value
|
||||
.split(/\r?\n/)
|
||||
.map(function (name) {
|
||||
const trimmed = name.trim()
|
||||
return /^[^\s]+$/.test(trimmed)
|
||||
? trimmed
|
||||
: ''
|
||||
})
|
||||
.filter(function (name) {
|
||||
// Filter out invalid and duplicate names
|
||||
return (!name || Object.prototype.hasOwnProperty.call(seen, name))
|
||||
? false
|
||||
: (seen[name] = true)
|
||||
})
|
||||
|
||||
// Update textarea with cleaned names
|
||||
textArea.value = names.join('\n')
|
||||
|
||||
if (!names.length)
|
||||
return swal('An error occurred!', 'You have not entered any upload names.', 'error')
|
||||
|
||||
page.postBulkDeleteUploads({
|
||||
field: 'name',
|
||||
values: names,
|
||||
cb (failed) {
|
||||
textArea.value = failed.join('\n')
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
page.postBulkDeleteUploads = function ({ field, values, cb } = {}) {
|
||||
const count = values.length
|
||||
const objective = `${values.length} upload${count === 1 ? '' : 's'}`
|
||||
let text = `You won't be able to recover ${objective}!`
|
||||
if (page.currentView === 'uploadsAll')
|
||||
text += '\nBe aware, you may be nuking uploads by other users!'
|
||||
|
||||
@ -925,41 +1002,32 @@ page.deleteSelectedFiles = function () {
|
||||
buttons: {
|
||||
cancel: true,
|
||||
confirm: {
|
||||
text: `Yes, nuke the ${suffix}!`,
|
||||
text: `Yes, nuke ${values.length === 1 ? 'it' : 'them'}!`,
|
||||
closeModal: false
|
||||
}
|
||||
}
|
||||
}).then(function (proceed) {
|
||||
if (!proceed) return
|
||||
|
||||
axios.post('api/upload/bulkdelete', {
|
||||
field: 'id',
|
||||
values: page.selected[page.currentView]
|
||||
}).then(function (bulkdelete) {
|
||||
if (!bulkdelete) return
|
||||
axios.post('api/upload/bulkdelete', { field, values }).then(function (response) {
|
||||
if (!response) return
|
||||
|
||||
if (bulkdelete.data.success === false)
|
||||
if (bulkdelete.data.description === 'No token provided') {
|
||||
if (response.data.success === false)
|
||||
if (response.data.description === 'No token provided') {
|
||||
return page.verifyToken(page.token)
|
||||
} else {
|
||||
return swal('An error occurred!', bulkdelete.data.description, 'error')
|
||||
return swal('An error occurred!', response.data.description, 'error')
|
||||
}
|
||||
|
||||
if (Array.isArray(bulkdelete.data.failed) && bulkdelete.data.failed.length) {
|
||||
page.selected[page.currentView] = page.selected[page.currentView].filter(function (id) {
|
||||
return bulkdelete.data.failed.includes(id)
|
||||
})
|
||||
localStorage[lsKeys.selected[page.currentView]] = JSON.stringify(page.selected[page.currentView])
|
||||
swal('An error ocurrred!', `From ${count} ${suffix}, unable to delete ${bulkdelete.data.failed.length} of them.`, 'error')
|
||||
} else {
|
||||
page.selected[page.currentView] = []
|
||||
delete localStorage[lsKeys.selected[page.currentView]]
|
||||
swal('Deleted!', `${count} ${suffix} ${count === 1 ? 'has' : 'have'} been deleted.`, 'success')
|
||||
}
|
||||
const failed = Array.isArray(response.data.failed) ? response.data.failed : []
|
||||
if (failed.length === values.length)
|
||||
swal('An error occurred!', `Unable to delete any of the ${objective}.`, 'error')
|
||||
else if (failed.length && failed.length < values.length)
|
||||
swal('Warning!', `From ${objective}, unable to delete ${failed.length} of them.`, 'warning')
|
||||
else
|
||||
swal('Deleted!', `${objective} ${count === 1 ? 'has' : 'have'} been deleted.`, 'success')
|
||||
|
||||
const views = Object.assign({}, page.views[page.currentView])
|
||||
views.autoPage = true
|
||||
page.getUploads(views)
|
||||
if (typeof cb === 'function') cb(failed)
|
||||
}).catch(function (error) {
|
||||
console.error(error)
|
||||
swal('An error occurred!', 'There was an error with the request, please check the console for more information.', 'error')
|
||||
@ -967,94 +1035,7 @@ page.deleteSelectedFiles = function () {
|
||||
})
|
||||
}
|
||||
|
||||
page.deleteByNames = function () {
|
||||
let appendix = ''
|
||||
if (page.permissions.moderator)
|
||||
appendix = '<br>As a staff, you can use this feature to delete uploads by other users.'
|
||||
|
||||
page.dom.innerHTML = `
|
||||
<h2 class="subtitle">Delete by names</h2>
|
||||
<div class="field">
|
||||
<label class="label">File names:</label>
|
||||
<div class="control">
|
||||
<textarea id="names" class="textarea"></textarea>
|
||||
</div>
|
||||
<p class="help">Separate each entry with a new line.${appendix}</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<a class="button is-danger is-fullwidth" data-action="delete-file-by-names">
|
||||
<span class="icon">
|
||||
<i class="icon-trash"></i>
|
||||
</span>
|
||||
<span>Bulk delete</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
page.fadeAndScroll()
|
||||
}
|
||||
|
||||
page.deleteFileByNames = function () {
|
||||
const names = document.querySelector('#names').value
|
||||
.split(/\r?\n/)
|
||||
.filter(function (n) {
|
||||
return n.trim().length
|
||||
})
|
||||
const count = names.length
|
||||
if (!count)
|
||||
return swal('An error occurred!', 'You have not entered any file names.', 'error')
|
||||
|
||||
const suffix = `file${count === 1 ? '' : 's'}`
|
||||
swal({
|
||||
title: 'Are you sure?',
|
||||
text: `You won't be able to recover ${count} ${suffix}!`,
|
||||
icon: 'warning',
|
||||
dangerMode: true,
|
||||
buttons: {
|
||||
cancel: true,
|
||||
confirm: {
|
||||
text: `Yes, nuke the ${suffix}!`,
|
||||
closeModal: false
|
||||
}
|
||||
}
|
||||
}).then(function (proceed) {
|
||||
if (!proceed) return
|
||||
|
||||
axios.post('api/upload/bulkdelete', {
|
||||
field: 'name',
|
||||
values: names
|
||||
}).then(function (bulkdelete) {
|
||||
if (!bulkdelete) return
|
||||
|
||||
if (bulkdelete.data.success === false)
|
||||
if (bulkdelete.data.description === 'No token provided') {
|
||||
return page.verifyToken(page.token)
|
||||
} else {
|
||||
return swal('An error occurred!', bulkdelete.data.description, 'error')
|
||||
}
|
||||
|
||||
if (Array.isArray(bulkdelete.data.failed) && bulkdelete.data.failed.length) {
|
||||
page.selected[page.currentView] = page.selected[page.currentView].filter(function (id) {
|
||||
return bulkdelete.data.failed.includes(id)
|
||||
})
|
||||
localStorage[lsKeys.selected[page.currentView]] = JSON.stringify(page.selected[page.currentView])
|
||||
swal('An error ocurrred!', `From ${count} ${suffix}, unable to delete ${bulkdelete.data.failed.length} of them.`, 'error')
|
||||
} else {
|
||||
page.selected[page.currentView] = []
|
||||
delete localStorage[lsKeys.selected[page.currentView]]
|
||||
swal('Deleted!', `${count} ${suffix} ${count === 1 ? 'has' : 'have'} been deleted.`, 'success')
|
||||
}
|
||||
|
||||
document.querySelector('#names').value = bulkdelete.data.failed.join('\n')
|
||||
}).catch(function (error) {
|
||||
console.error(error)
|
||||
swal('An error occurred!', 'There was an error with the request, please check the console for more information.', 'error')
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
page.addSelectedFilesToAlbum = function () {
|
||||
page.addSelectedUploadsToAlbum = function () {
|
||||
if (page.currentView !== 'uploads')
|
||||
return
|
||||
|
||||
@ -1062,7 +1043,7 @@ page.addSelectedFilesToAlbum = function () {
|
||||
if (!count)
|
||||
return swal('An error occurred!', 'You have not selected any uploads.', 'error')
|
||||
|
||||
page.addFilesToAlbum(page.selected[page.currentView], function (failed) {
|
||||
page.addUploadsToAlbum(page.selected[page.currentView], function (failed) {
|
||||
if (!failed) return
|
||||
if (failed.length)
|
||||
page.selected[page.currentView] = page.selected[page.currentView].filter(function (id) {
|
||||
@ -1076,21 +1057,21 @@ page.addSelectedFilesToAlbum = function () {
|
||||
})
|
||||
}
|
||||
|
||||
page.addSingleFileToAlbum = function (id) {
|
||||
page.addFilesToAlbum([id], function (failed) {
|
||||
page.addToAlbum = function (id) {
|
||||
page.addUploadsToAlbum([id], function (failed) {
|
||||
if (!failed) return
|
||||
page.getUploads(page.views[page.currentView])
|
||||
})
|
||||
}
|
||||
|
||||
page.addFilesToAlbum = function (ids, callback) {
|
||||
page.addUploadsToAlbum = function (ids, callback) {
|
||||
const count = ids.length
|
||||
|
||||
const content = document.createElement('div')
|
||||
content.innerHTML = `
|
||||
<div class="field has-text-centered">
|
||||
<p>You are about to add <b>${count}</b> file${count === 1 ? '' : 's'} to an album.</p>
|
||||
<p><b>If a file is already in an album, it will be moved.</b></p>
|
||||
<p>You are about to add <b>${count}</b> upload${count === 1 ? '' : 's'} to an album.</p>
|
||||
<p><b>If an upload is already in an album, it will be moved.</b></p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
@ -1140,7 +1121,7 @@ page.addFilesToAlbum = function (ids, callback) {
|
||||
if (add.data.failed && add.data.failed.length)
|
||||
added -= add.data.failed.length
|
||||
|
||||
const suffix = `file${ids.length === 1 ? '' : 's'}`
|
||||
const suffix = `upload${ids.length === 1 ? '' : 's'}`
|
||||
if (!added)
|
||||
return swal('An error occurred!', `Could not add the ${suffix} to the album.`, 'error')
|
||||
|
||||
@ -1469,9 +1450,17 @@ page.getAlbumsSidebar = function () {
|
||||
}
|
||||
|
||||
const albumsContainer = document.querySelector('#albumsContainer')
|
||||
albumsContainer.innerHTML = ''
|
||||
|
||||
if (response.data.albums === undefined) return
|
||||
// Clear albums sidebar if necessary
|
||||
const oldAlbums = albumsContainer.querySelectorAll('li > a')
|
||||
if (oldAlbums.length) {
|
||||
for (let i = 0; i < oldAlbums.length; i++)
|
||||
page.menus.splice(page.menus.indexOf(oldAlbums[i]), 1)
|
||||
albumsContainer.innerHTML = ''
|
||||
}
|
||||
|
||||
if (response.data.albums === undefined)
|
||||
return
|
||||
|
||||
for (let i = 0; i < response.data.albums.length; i++) {
|
||||
const album = response.data.albums[i]
|
||||
@ -1481,8 +1470,10 @@ page.getAlbumsSidebar = function () {
|
||||
a.innerHTML = album.name
|
||||
|
||||
a.addEventListener('click', function () {
|
||||
page.getAlbum(this)
|
||||
page.getUploads({ album: this.id })
|
||||
page.setActiveMenu(this)
|
||||
})
|
||||
page.menus.push(a)
|
||||
|
||||
li.appendChild(a)
|
||||
albumsContainer.appendChild(li)
|
||||
@ -1493,11 +1484,6 @@ page.getAlbumsSidebar = function () {
|
||||
})
|
||||
}
|
||||
|
||||
page.getAlbum = function (album) {
|
||||
page.setActiveMenu(album)
|
||||
page.getUploads({ album: album.id })
|
||||
}
|
||||
|
||||
page.changeToken = function () {
|
||||
axios.get('api/tokens').then(function (response) {
|
||||
if (response.data.success === false)
|
||||
@ -1508,7 +1494,6 @@ page.changeToken = function () {
|
||||
}
|
||||
|
||||
page.dom.innerHTML = `
|
||||
<h2 class="subtitle">Manage your token</h2>
|
||||
<div class="field">
|
||||
<label class="label">Your current token:</label>
|
||||
<div class="field">
|
||||
@ -1567,7 +1552,6 @@ page.getNewToken = function (element) {
|
||||
|
||||
page.changePassword = function () {
|
||||
page.dom.innerHTML = `
|
||||
<h2 class="subtitle">Change your password</h2>
|
||||
<form class="prevent-default">
|
||||
<div class="field">
|
||||
<label class="label">New password:</label>
|
||||
@ -1634,13 +1618,11 @@ page.sendNewPassword = function (pass, element) {
|
||||
})
|
||||
}
|
||||
|
||||
page.setActiveMenu = function (activeItem) {
|
||||
const menu = document.querySelector('#menu')
|
||||
const items = menu.getElementsByTagName('a')
|
||||
for (let i = 0; i < items.length; i++)
|
||||
items[i].classList.remove('is-active')
|
||||
page.setActiveMenu = function (element) {
|
||||
for (let i = 0; i < page.menus.length; i++)
|
||||
page.menus[i].classList.remove('is-active')
|
||||
|
||||
activeItem.classList.add('is-active')
|
||||
element.classList.add('is-active')
|
||||
}
|
||||
|
||||
page.getUsers = function ({ pageNum } = {}, element) {
|
||||
@ -1717,7 +1699,8 @@ page.getUsers = function ({ pageNum } = {}, element) {
|
||||
</div>
|
||||
`
|
||||
|
||||
let allSelected = true
|
||||
// Whether there are any unselected items
|
||||
let unselected = false
|
||||
|
||||
page.dom.innerHTML = `
|
||||
${pagination}
|
||||
@ -1727,7 +1710,7 @@ page.getUsers = function ({ pageNum } = {}, element) {
|
||||
<table class="table is-narrow is-fullwidth is-hoverable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><input id="selectAll" class="checkbox" type="checkbox" title="Select all users" data-action="select-all"></th>
|
||||
<th><input id="selectAll" class="checkbox" type="checkbox" title="Select all" data-action="select-all"></th>
|
||||
<th>ID</th>
|
||||
<th style="width: 20%">Username</th>
|
||||
<th>Uploads</th>
|
||||
@ -1749,7 +1732,7 @@ page.getUsers = function ({ pageNum } = {}, element) {
|
||||
for (let i = 0; i < response.data.users.length; i++) {
|
||||
const user = response.data.users[i]
|
||||
const selected = page.selected.users.includes(user.id)
|
||||
if (!selected && allSelected) allSelected = false
|
||||
if (!selected) unselected = true
|
||||
|
||||
let displayGroup = null
|
||||
const groups = Object.keys(user.groups)
|
||||
@ -1770,11 +1753,11 @@ page.getUsers = function ({ pageNum } = {}, element) {
|
||||
const tr = document.createElement('tr')
|
||||
tr.dataset.id = user.id
|
||||
tr.innerHTML = `
|
||||
<td class="controls"><input type="checkbox" class="checkbox" title="Select this user" data-action="select"${selected ? ' checked' : ''}></td>
|
||||
<td class="controls"><input type="checkbox" class="checkbox" title="Select" data-action="select"${selected ? ' checked' : ''}></td>
|
||||
<th>${user.id}</th>
|
||||
<th${enabled ? '' : ' class="is-linethrough"'}>${user.username}</td>
|
||||
<th>${user.uploadsCount}</th>
|
||||
<td>${page.getPrettyBytes(user.diskUsage)}</td>
|
||||
<th>${user.uploads}</th>
|
||||
<td>${page.getPrettyBytes(user.usage)}</td>
|
||||
<td>${displayGroup}</td>
|
||||
<td class="controls" style="text-align: right">
|
||||
<a class="button is-small is-primary" title="Edit user" data-action="edit-user">
|
||||
@ -1803,13 +1786,15 @@ page.getUsers = function ({ pageNum } = {}, element) {
|
||||
table.appendChild(tr)
|
||||
page.checkboxes.users = Array.from(table.querySelectorAll('.checkbox[data-action="select"]'))
|
||||
}
|
||||
page.fadeAndScroll()
|
||||
|
||||
if (allSelected && response.data.users.length) {
|
||||
const selectAll = document.querySelector('#selectAll')
|
||||
if (selectAll) selectAll.checked = true
|
||||
const selectAll = document.querySelector('#selectAll')
|
||||
if (selectAll && !unselected) {
|
||||
selectAll.checked = true
|
||||
selectAll.title = 'Unselect all'
|
||||
}
|
||||
|
||||
page.fadeAndScroll()
|
||||
|
||||
page.views.users.pageNum = response.data.users.length ? pageNum : 0
|
||||
}).catch(function (error) {
|
||||
if (element) page.isLoading(element, false)
|
||||
@ -2008,7 +1993,7 @@ page.paginate = function (totalItems, itemsPerPage, currentPage) {
|
||||
`
|
||||
}
|
||||
|
||||
page.getServerStats = function (element) {
|
||||
page.getStatistics = function (element) {
|
||||
if (!page.permissions.admin)
|
||||
return swal('An error occurred!', 'You can not do this!', 'error')
|
||||
|
||||
@ -2016,7 +2001,6 @@ page.getServerStats = function (element) {
|
||||
Please wait, this may take a while\u2026
|
||||
<progress class="progress is-breeze" max="100" style="margin-top: 10px"></progress>
|
||||
`
|
||||
page.fadeAndScroll()
|
||||
|
||||
const url = 'api/stats'
|
||||
axios.get(url).then(function (response) {
|
||||
@ -2040,20 +2024,31 @@ page.getServerStats = function (element) {
|
||||
`
|
||||
else
|
||||
try {
|
||||
const types = response.data.stats[keys[i]]._types || {}
|
||||
const valKeys = Object.keys(response.data.stats[keys[i]])
|
||||
for (let j = 0; j < valKeys.length; j++) {
|
||||
const _value = response.data.stats[keys[i]][valKeys[j]]
|
||||
let value = _value
|
||||
if (['albums', 'users', 'uploads'].includes(keys[i]))
|
||||
value = _value.toLocaleString()
|
||||
if (['memoryUsage', 'size'].includes(valKeys[j]))
|
||||
value = page.getPrettyBytes(_value)
|
||||
if (valKeys[j] === 'systemMemory')
|
||||
value = `${page.getPrettyBytes(_value.used)} / ${page.getPrettyBytes(_value.total)} (${Math.round(_value.used / _value.total * 100)}%)`
|
||||
// Skip keys that starts with an underscore
|
||||
if (/^_/.test(valKeys[j]))
|
||||
continue
|
||||
|
||||
const value = response.data.stats[keys[i]][valKeys[j]]
|
||||
let parsed = value
|
||||
|
||||
// Parse values with some preset formatting
|
||||
if ((types.number || []).includes(valKeys[j]))
|
||||
parsed = value.toLocaleString()
|
||||
if ((types.byte || []).includes(valKeys[j]))
|
||||
parsed = page.getPrettyBytes(value)
|
||||
if ((types.byteUsage || []).includes(valKeys[j]))
|
||||
parsed = `${page.getPrettyBytes(value.used)} / ${page.getPrettyBytes(value.total)} (${Math.round(value.used / value.total * 100)}%)`
|
||||
|
||||
const string = valKeys[j]
|
||||
.replace(/([A-Z])/g, ' $1')
|
||||
.toUpperCase()
|
||||
rows += `
|
||||
<tr>
|
||||
<th>${valKeys[j].replace(/([A-Z])/g, ' $1').toUpperCase()}</th>
|
||||
<td>${value}</td>
|
||||
<th>${string}</th>
|
||||
<td>${parsed}</td>
|
||||
</tr>
|
||||
`
|
||||
}
|
||||
@ -2084,11 +2079,16 @@ page.getServerStats = function (element) {
|
||||
`
|
||||
}
|
||||
|
||||
page.dom.innerHTML = `
|
||||
<h2 class="subtitle">Statistics</h2>
|
||||
${content}
|
||||
`
|
||||
page.dom.innerHTML = content
|
||||
page.fadeAndScroll()
|
||||
}).catch(function (error) {
|
||||
console.error(error)
|
||||
const description = error.response.data && error.response.data.description
|
||||
? error.response.data && error.response.data.description
|
||||
: 'There was an error with the request, please check the console for more information.'
|
||||
page.dom.innerHTML = `<p>${description}</p>`
|
||||
page.fadeAndScroll()
|
||||
return swal('An error occurred!', description, 'error')
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -55,11 +55,12 @@ page.checkIfPublic = function () {
|
||||
page.preparePage()
|
||||
}).catch(function (error) {
|
||||
console.error(error)
|
||||
document.querySelector('#albumDiv').style.display = 'none'
|
||||
document.querySelector('#tabs').style.display = 'none'
|
||||
document.querySelector('#albumDiv').classList.add('is-hidden')
|
||||
document.querySelector('#tabs').classList.add('is-hidden')
|
||||
const button = document.querySelector('#loginToUpload')
|
||||
button.classList.remove('is-loading')
|
||||
button.innerText = 'Error occurred. Reload the page?'
|
||||
button.classList.remove('is-loading')
|
||||
button.classList.remove('is-hidden')
|
||||
return swal('An error occurred!', 'There was an error with the request, please check the console for more information.', 'error')
|
||||
})
|
||||
}
|
||||
@ -74,9 +75,9 @@ page.preparePage = function () {
|
||||
button.classList.remove('is-loading')
|
||||
|
||||
if (page.enableUserAccounts)
|
||||
button.innerText = 'Anonymous upload is disabled. Log in to page.'
|
||||
button.innerText = 'Anonymous upload is disabled. Log in to upload.'
|
||||
else
|
||||
button.innerText = 'Running in private mode. Log in to page.'
|
||||
button.innerText = 'Running in private mode. Log in to upload.'
|
||||
}
|
||||
else
|
||||
return page.prepareUpload()
|
||||
@ -120,13 +121,13 @@ page.prepareUpload = function () {
|
||||
page.prepareAlbums()
|
||||
|
||||
// Display the album selection
|
||||
document.querySelector('#albumDiv').style.display = 'flex'
|
||||
document.querySelector('#albumDiv').classList.remove('is-hidden')
|
||||
}
|
||||
|
||||
page.prepareUploadConfig()
|
||||
|
||||
document.querySelector('#maxSize').innerHTML = `Maximum upload size per file is ${page.getPrettyBytes(page.maxSizeBytes)}`
|
||||
document.querySelector('#loginToUpload').style.display = 'none'
|
||||
document.querySelector('#loginToUpload').classList.add('is-hidden')
|
||||
|
||||
if (!page.token && page.enableUserAccounts)
|
||||
document.querySelector('#loginLinkText').innerHTML = 'Create an account and keep track of your uploads'
|
||||
@ -158,7 +159,7 @@ page.prepareUpload = function () {
|
||||
page.setActiveTab(this.dataset.id)
|
||||
})
|
||||
page.setActiveTab('tab-files')
|
||||
tabs.style.display = 'flex'
|
||||
tabs.classList.remove('is-hidden')
|
||||
}
|
||||
|
||||
page.prepareAlbums = function () {
|
||||
@ -203,10 +204,10 @@ page.setActiveTab = function (tabId) {
|
||||
const id = page.tabs[i].dataset.id
|
||||
if (id === tabId) {
|
||||
page.tabs[i].classList.add('is-active')
|
||||
document.querySelector(`#${id}`).style.display = 'block'
|
||||
document.querySelector(`#${id}`).classList.remove('is-hidden')
|
||||
} else {
|
||||
page.tabs[i].classList.remove('is-active')
|
||||
document.querySelector(`#${id}`).style.display = 'none'
|
||||
document.querySelector(`#${id}`).classList.add('is-hidden')
|
||||
}
|
||||
}
|
||||
page.activeTab = tabId
|
||||
@ -268,7 +269,7 @@ page.prepareDropzone = function () {
|
||||
}
|
||||
}
|
||||
}).then(function (response) {
|
||||
file.previewElement.querySelector('.progress').style.display = 'none'
|
||||
file.previewElement.querySelector('.progress').classList.add('is-hidden')
|
||||
|
||||
if (response.data.success === false)
|
||||
file.previewElement.querySelector('.error').innerHTML = response.data.description
|
||||
@ -285,7 +286,7 @@ page.prepareDropzone = function () {
|
||||
// Set active tab to file uploads
|
||||
page.setActiveTab('tab-files')
|
||||
// Add file entry
|
||||
tabDiv.querySelector('.uploads').style.display = 'block'
|
||||
tabDiv.querySelector('.uploads').classList.remove('is-hidden')
|
||||
file.previewElement.querySelector('.name').innerHTML = file.name
|
||||
})
|
||||
|
||||
@ -308,7 +309,7 @@ page.prepareDropzone = function () {
|
||||
|
||||
page.dropzone.on('success', function (file, response) {
|
||||
if (!response) return
|
||||
file.previewElement.querySelector('.progress').style.display = 'none'
|
||||
file.previewElement.querySelector('.progress').classList.add('is-hidden')
|
||||
|
||||
if (response.success === false)
|
||||
file.previewElement.querySelector('.error').innerHTML = response.description
|
||||
@ -324,7 +325,7 @@ page.prepareDropzone = function () {
|
||||
error = `File too large (${page.getPrettyBytes(file.size)}).`
|
||||
|
||||
page.updateTemplateIcon(file.previewElement, 'icon-block')
|
||||
file.previewElement.querySelector('.progress').style.display = 'none'
|
||||
file.previewElement.querySelector('.progress').classList.add('is-hidden')
|
||||
file.previewElement.querySelector('.name').innerHTML = file.name
|
||||
file.previewElement.querySelector('.error').innerHTML = error.description || error
|
||||
})
|
||||
@ -362,7 +363,7 @@ page.uploadUrls = function (button) {
|
||||
// eslint-disable-next-line prefer-promise-reject-errors
|
||||
return done('You have not entered any URLs.')
|
||||
|
||||
tabDiv.querySelector('.uploads').style.display = 'block'
|
||||
tabDiv.querySelector('.uploads').classList.remove('is-hidden')
|
||||
const files = urls.map(function (url) {
|
||||
const previewTemplate = document.createElement('template')
|
||||
previewTemplate.innerHTML = page.previewTemplate.trim()
|
||||
@ -377,7 +378,7 @@ page.uploadUrls = function (button) {
|
||||
return done()
|
||||
|
||||
function posted (result) {
|
||||
files[i].previewElement.querySelector('.progress').style.display = 'none'
|
||||
files[i].previewElement.querySelector('.progress').classList.add('is-hidden')
|
||||
if (result.success) {
|
||||
page.updateTemplate(files[i], result.files[0])
|
||||
} else {
|
||||
@ -408,7 +409,7 @@ page.updateTemplateIcon = function (templateElement, iconClass) {
|
||||
const iconElement = templateElement.querySelector('.icon')
|
||||
if (!iconElement) return
|
||||
iconElement.classList.add(iconClass)
|
||||
iconElement.style.display = ''
|
||||
iconElement.classList.remove('is-hidden')
|
||||
}
|
||||
|
||||
page.updateTemplate = function (file, response) {
|
||||
@ -417,19 +418,19 @@ page.updateTemplate = function (file, response) {
|
||||
const a = file.previewElement.querySelector('.link > a')
|
||||
const clipboard = file.previewElement.querySelector('.clipboard-mobile > .clipboard-js')
|
||||
a.href = a.innerHTML = clipboard.dataset.clipboardText = response.url
|
||||
clipboard.parentElement.style.display = 'block'
|
||||
clipboard.parentElement.classList.remove('is-hidden')
|
||||
|
||||
const exec = /.[\w]+(\?|$)/.exec(response.url)
|
||||
if (exec && exec[0] && page.imageExtensions.includes(exec[0].toLowerCase())) {
|
||||
const img = file.previewElement.querySelector('img')
|
||||
img.setAttribute('alt', response.name || '')
|
||||
img.dataset.src = response.url
|
||||
img.style.display = ''
|
||||
img.classList.remove('is-hidden')
|
||||
img.onerror = function () {
|
||||
// Hide image elements that fail to load
|
||||
// Consequently include WEBP in browsers that do not have WEBP support (Firefox/IE)
|
||||
this.style.display = 'none'
|
||||
file.previewElement.querySelector('.icon').style.display = ''
|
||||
this.classList.add('is-hidden')
|
||||
file.previewElement.querySelector('.icon').classList.remove('is-hidden')
|
||||
}
|
||||
page.lazyLoad.update(file.previewElement.querySelectorAll('img'))
|
||||
} else {
|
||||
@ -439,7 +440,7 @@ page.updateTemplate = function (file, response) {
|
||||
if (response.expirydate) {
|
||||
const expiryDate = file.previewElement.querySelector('.expiry-date')
|
||||
expiryDate.innerHTML = `Expiry date: ${page.getPrettyDate(new Date(response.expirydate * 1000))}`
|
||||
expiryDate.style.display = 'block'
|
||||
expiryDate.classList.remove('is-hidden')
|
||||
}
|
||||
}
|
||||
|
||||
@ -572,7 +573,7 @@ page.prepareUploadConfig = function () {
|
||||
page.fileLength = stored
|
||||
}
|
||||
|
||||
fileLengthDiv.style.display = 'block'
|
||||
fileLengthDiv.classList.remove('is-hidden')
|
||||
fileLengthDiv.querySelector('.help').innerHTML = helpText
|
||||
}
|
||||
|
||||
@ -597,7 +598,7 @@ page.prepareUploadConfig = function () {
|
||||
page.uploadAge = stored
|
||||
}
|
||||
}
|
||||
uploadAgeDiv.style.display = 'block'
|
||||
uploadAgeDiv.classList.remove('is-hidden')
|
||||
}
|
||||
|
||||
const tabContent = document.querySelector('#tab-config')
|
||||
@ -664,8 +665,9 @@ window.addEventListener('paste', function (event) {
|
||||
const item = items[index[i]]
|
||||
if (item.kind === 'file') {
|
||||
const blob = item.getAsFile()
|
||||
const file = new File([blob], `pasted-image.${blob.type.match(/(?:[^/]*\/)([^;]*)/)[1]}`)
|
||||
file.type = blob.type
|
||||
const file = new File([blob], `pasted-image.${blob.type.match(/(?:[^/]*\/)([^;]*)/)[1]}`, {
|
||||
type: blob.type
|
||||
})
|
||||
page.dropzone.addFile(file)
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,7 @@
|
||||
v3: CSS and JS files (libs such as bulma, lazyload, etc).
|
||||
v4: Renders in /public/render/* directories (to be used by render.js).
|
||||
#}
|
||||
{% set v1 = "o59f6p3y1F" %}
|
||||
{% set v1 = "1QupbESWeT" %}
|
||||
{% set v2 = "hiboQUzAzp" %}
|
||||
{% set v3 = "tWLiAlAX5i" %}
|
||||
{% set v4 = "S3TAWpPeFS" %}
|
||||
|
@ -20,7 +20,7 @@
|
||||
|
||||
{% block content %}
|
||||
{{ super() }}
|
||||
<section id="dashboard" class="section">
|
||||
<section id="dashboard" class="section is-hidden">
|
||||
<div id="panel" class="container">
|
||||
<h1 class="title">
|
||||
Dashboard
|
||||
@ -42,28 +42,28 @@
|
||||
<a id="itemUploads">Uploads</a>
|
||||
</li>
|
||||
<li>
|
||||
<a id="itemDeleteByNames">Delete by names</a>
|
||||
<a id="itemDeleteUploadsByNames">Delete uploads by names</a>
|
||||
</li>
|
||||
</ul>
|
||||
<p class="menu-label">Albums</p>
|
||||
<ul class="menu-list">
|
||||
<li>
|
||||
<a id="itemManageGallery">Manage your albums</a>
|
||||
<a id="itemManageAlbums">Manage your albums</a>
|
||||
</li>
|
||||
<li>
|
||||
<ul id="albumsContainer"></ul>
|
||||
</li>
|
||||
</ul>
|
||||
<p id="itemLabelAdmin" class="menu-label" style="display: none">Administration</p>
|
||||
<ul id="itemListAdmin" class="menu-list" style="display: none">
|
||||
<p id="itemLabelAdmin" class="menu-label is-hidden">Administration</p>
|
||||
<ul id="itemListAdmin" class="menu-list is-hidden">
|
||||
<li>
|
||||
<a id="itemServerStats">Statistics</a>
|
||||
<a id="itemStatistics" class="is-hidden">Statistics</a>
|
||||
</li>
|
||||
<li>
|
||||
<a id="itemManageUploads">Manage uploads</a>
|
||||
<a id="itemManageUploads" class="is-hidden">Manage uploads</a>
|
||||
</li>
|
||||
<li>
|
||||
<a id="itemManageUsers">Manage users</a>
|
||||
<a id="itemManageUsers" class="is-hidden">Manage users</a>
|
||||
</li>
|
||||
</ul>
|
||||
<p class="menu-label">Configuration</p>
|
||||
@ -72,10 +72,10 @@
|
||||
<a id="ShareX">ShareX user profile</a>
|
||||
</li>
|
||||
<li>
|
||||
<a id="itemTokens">Manage your token</a>
|
||||
<a id="itemManageToken">Manage your token</a>
|
||||
</li>
|
||||
<li>
|
||||
<a id="itemPassword">Change your password</a>
|
||||
<a id="itemChangePassword">Change your password</a>
|
||||
</li>
|
||||
<li>
|
||||
<a id="itemLogout">Logout</a>
|
||||
|
@ -43,8 +43,8 @@
|
||||
<div class="columns is-gapless">
|
||||
<div class="column is-hidden-mobile"></div>
|
||||
<div class="column">
|
||||
<a id="loginToUpload" class="button is-danger is-loading" style="display: flex"></a>
|
||||
<div id="albumDiv" class="field has-addons" style="display: none">
|
||||
<button id="loginToUpload" class="button is-danger is-fullwidth is-loading"></button>
|
||||
<div id="albumDiv" class="field has-addons is-hidden">
|
||||
<div class="control is-expanded">
|
||||
<div class="select is-fullwidth">
|
||||
<select id="albumSelect"></select>
|
||||
@ -56,7 +56,7 @@
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div id="tabs" class="tabs is-centered is-boxed" style="display: none">
|
||||
<div id="tabs" class="tabs is-centered is-boxed is-hidden">
|
||||
<ul>
|
||||
<li data-id="tab-files" class="is-active">
|
||||
<a>
|
||||
@ -80,12 +80,12 @@
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div id="tab-files" class="tab-content" style="display: none">
|
||||
<div id="tab-files" class="tab-content is-hidden">
|
||||
<div class="field dz-container"></div>
|
||||
<div class="field uploads"></div>
|
||||
</div>
|
||||
{% if urlMaxSize -%}
|
||||
<div id="tab-urls" class="tab-content" style="display: none">
|
||||
<div id="tab-urls" class="tab-content is-hidden">
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<textarea id="urls" class="textarea" rows="2"></textarea>
|
||||
@ -119,7 +119,7 @@
|
||||
<div class="field uploads"></div>
|
||||
</div>
|
||||
{%- endif %}
|
||||
<div id="tab-config" class="tab-content" style="display: none">
|
||||
<div id="tab-config" class="tab-content is-hidden">
|
||||
<form>
|
||||
<div class="field">
|
||||
<label class="label">File size display</label>
|
||||
@ -133,7 +133,7 @@
|
||||
</div>
|
||||
<p class="help">This will be used in our homepage, dashboard, and album public pages.</p>
|
||||
</div>
|
||||
<div id="fileLengthDiv" class="field" style="display: none">
|
||||
<div id="fileLengthDiv" class="field is-hidden">
|
||||
<label class="label">File identifier length</label>
|
||||
<div class="control is-expanded">
|
||||
<input id="fileLength" class="input is-fullwidth" type="number" min="0">
|
||||
@ -141,7 +141,7 @@
|
||||
<p class="help"></p>
|
||||
</div>
|
||||
{%- if temporaryUploadAges %}
|
||||
<div id="uploadAgeDiv" class="field" style="display: none">
|
||||
<div id="uploadAgeDiv" class="field is-hidden">
|
||||
<label class="label">Upload age</label>
|
||||
<div class="control is-expanded">
|
||||
<div class="select is-fullwidth">
|
||||
@ -185,18 +185,18 @@
|
||||
<div class="column is-hidden-mobile"></div>
|
||||
</div>
|
||||
|
||||
<div id="tpl" style="display: none">
|
||||
<div id="tpl" class="is-hidden">
|
||||
<div class="field">
|
||||
<i class="icon" style="display: none"></i>
|
||||
<img class="is-unselectable" style="display: none">
|
||||
<i class="icon is-hidden"></i>
|
||||
<img class="is-unselectable is-hidden">
|
||||
<p class="name is-unselectable"></p>
|
||||
<progress class="progress is-small is-danger" max="100" value="0"></progress>
|
||||
<p class="error"></p>
|
||||
<p class="link">
|
||||
<a target="_blank" rel="noopener"></a>
|
||||
</p>
|
||||
<p class="help expiry-date" style="display: none"></p>
|
||||
<p class="clipboard-mobile" style="display: none">
|
||||
<p class="help expiry-date is-hidden"></p>
|
||||
<p class="clipboard-mobile is-hidden">
|
||||
<a class="button is-small is-info is-outlined clipboard-js" style="display: flex">
|
||||
<span class="icon">
|
||||
<i class="icon-clipboard-1"></i>
|
||||
|
Loading…
Reference in New Issue
Block a user