mirror of
https://github.com/BobbyWibowo/lolisafe.git
synced 2024-12-14 00:16:21 +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)
|
.offset(25 * offset)
|
||||||
.select('id', 'username', 'enabled', 'permission')
|
.select('id', 'username', 'enabled', 'permission')
|
||||||
|
|
||||||
const userids = []
|
const pointers = {}
|
||||||
|
|
||||||
for (const user of users) {
|
for (const user of users) {
|
||||||
user.groups = perms.mapPermissions(user)
|
user.groups = perms.mapPermissions(user)
|
||||||
delete user.permission
|
delete user.permission
|
||||||
|
user.uploads = 0
|
||||||
userids.push(user.id)
|
user.usage = 0
|
||||||
user.uploadsCount = 0
|
pointers[user.id] = user
|
||||||
user.diskUsage = 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const maps = {}
|
|
||||||
const uploads = await db.table('files')
|
const uploads = await db.table('files')
|
||||||
.whereIn('userid', userids)
|
.whereIn('userid', Object.keys(pointers))
|
||||||
|
.select('userid', 'size')
|
||||||
|
|
||||||
for (const upload of uploads) {
|
for (const upload of uploads) {
|
||||||
if (maps[upload.userid] === undefined)
|
pointers[upload.userid].uploads++
|
||||||
maps[upload.userid] = { count: 0, size: 0 }
|
pointers[upload.userid].usage += parseInt(upload.size)
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.json({ success: true, users, count })
|
return res.json({ success: true, users, count })
|
||||||
|
@ -16,7 +16,8 @@ const fsFuncs = [
|
|||||||
'rename',
|
'rename',
|
||||||
'rmdir',
|
'rmdir',
|
||||||
'symlink',
|
'symlink',
|
||||||
'unlink'
|
'unlink',
|
||||||
|
'writeFile'
|
||||||
]
|
]
|
||||||
|
|
||||||
for (const fsFunc of fsFuncs)
|
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 name = await self.getUniqueRandomName(length, extname)
|
||||||
|
|
||||||
const destination = path.join(paths.uploads, name)
|
const destination = path.join(paths.uploads, name)
|
||||||
await new Promise((resolve, reject) => {
|
await paths.writeFile(destination, file)
|
||||||
fs.writeFile(destination, file, error => {
|
|
||||||
if (error) return reject(error)
|
|
||||||
return resolve()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
downloaded.push(destination)
|
downloaded.push(destination)
|
||||||
|
|
||||||
infoMap.push({
|
infoMap.push({
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
const { promisify } = require('util')
|
const { promisify } = require('util')
|
||||||
|
const { spawn } = require('child_process')
|
||||||
const config = require('./../config')
|
const config = require('./../config')
|
||||||
const db = require('knex')(config.database)
|
const db = require('knex')(config.database)
|
||||||
const fetch = require('node-fetch')
|
const fetch = require('node-fetch')
|
||||||
@ -33,6 +34,10 @@ const statsCache = {
|
|||||||
cache: null,
|
cache: null,
|
||||||
generating: false
|
generating: false
|
||||||
},
|
},
|
||||||
|
disk: {
|
||||||
|
cache: null,
|
||||||
|
generating: false
|
||||||
|
},
|
||||||
albums: {
|
albums: {
|
||||||
cache: null,
|
cache: null,
|
||||||
generating: false,
|
generating: false,
|
||||||
@ -500,153 +505,287 @@ self.stats = async (req, res, next) => {
|
|||||||
const isadmin = perms.is(user, 'admin')
|
const isadmin = perms.is(user, 'admin')
|
||||||
if (!isadmin) return res.status(403).end()
|
if (!isadmin) return res.status(403).end()
|
||||||
|
|
||||||
const stats = {}
|
try {
|
||||||
|
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
|
|
||||||
|
|
||||||
const os = await si.osInfo()
|
const os = await si.osInfo()
|
||||||
const currentLoad = await si.currentLoad()
|
|
||||||
const mem = await si.mem()
|
|
||||||
|
|
||||||
stats.system = {
|
// System info
|
||||||
platform: `${os.platform} ${os.arch}`,
|
if (!statsCache.system.cache && statsCache.system.generating) {
|
||||||
distro: `${os.distro} ${os.release}`,
|
stats.system = false
|
||||||
kernel: os.kernel,
|
} else if (statsCache.system.generating) {
|
||||||
cpuLoad: `${currentLoad.currentload.toFixed(1)}%`,
|
stats.system = statsCache.system.cache
|
||||||
cpusLoad: currentLoad.cpus.map(cpu => `${cpu.load.toFixed(1)}%`).join(', '),
|
} else {
|
||||||
systemMemory: {
|
statsCache.system.generating = true
|
||||||
used: mem.active,
|
|
||||||
total: mem.total
|
const currentLoad = await si.currentLoad()
|
||||||
},
|
const mem = await si.mem()
|
||||||
memoryUsage: process.memoryUsage().rss,
|
|
||||||
nodeVersion: `${process.versions.node}`
|
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
|
// Disk usage, only for Linux platform
|
||||||
statsCache.system.cache = stats.system
|
if (os.platform === 'linux')
|
||||||
statsCache.system.generating = false
|
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) {
|
// We pre-assign the keys below to guarantee their order
|
||||||
stats.albums = false
|
stats.disk = {
|
||||||
} else if ((statsCache.albums.invalidatedAt < statsCache.albums.generatedAt) || statsCache.albums.generating) {
|
_types: {
|
||||||
stats.albums = statsCache.albums.cache
|
byte: ['uploads', 'thumbs', 'zips', 'chunks'],
|
||||||
} else {
|
byteUsage: ['drive']
|
||||||
statsCache.albums.generating = true
|
},
|
||||||
stats.albums = {
|
drive: null,
|
||||||
total: 0,
|
uploads: 0,
|
||||||
active: 0,
|
thumbs: 0,
|
||||||
downloadable: 0,
|
zips: 0,
|
||||||
public: 0,
|
chunks: 0
|
||||||
zips: 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')
|
// Users
|
||||||
stats.albums.total = albums.length
|
if (!statsCache.users.cache && statsCache.users.generating) {
|
||||||
const identifiers = []
|
stats.users = false
|
||||||
for (const album of albums)
|
} else if ((statsCache.users.invalidatedAt < statsCache.users.generatedAt) || statsCache.users.generating) {
|
||||||
if (album.enabled) {
|
stats.users = statsCache.users.cache
|
||||||
stats.albums.active++
|
} 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.download) stats.albums.downloadable++
|
||||||
if (album.public) stats.albums.public++
|
if (album.public) stats.albums.public++
|
||||||
if (album.zipGeneratedAt) identifiers.push(album.identifier)
|
if (album.zipGeneratedAt) identifiers.push(album.identifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
const zipsDir = path.join(paths.uploads, 'zips')
|
for (const identifier of identifiers)
|
||||||
await Promise.all(identifiers.map(identifier => {
|
try {
|
||||||
return new Promise(resolve => {
|
await paths.access(path.join(paths.zips, `${identifier}.zip`))
|
||||||
const filePath = path.join(zipsDir, `${identifier}.zip`)
|
stats.albums.zipGenerated++
|
||||||
fs.access(filePath, error => {
|
} catch (error) {
|
||||||
if (!error) stats.albums.zips++
|
// Re-throw error
|
||||||
resolve(true)
|
if (error.code !== 'ENOENT')
|
||||||
})
|
throw error
|
||||||
})
|
|
||||||
}))
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update cache
|
||||||
|
statsCache.albums.cache = stats.albums
|
||||||
|
statsCache.albums.generatedAt = Date.now()
|
||||||
|
statsCache.albums.generating = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update cache
|
return res.json({ success: true, stats })
|
||||||
statsCache.users.cache = stats.users
|
} catch (error) {
|
||||||
statsCache.users.generatedAt = Date.now()
|
logger.error(error)
|
||||||
statsCache.users.generating = false
|
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
|
module.exports = self
|
||||||
|
@ -3,9 +3,7 @@ body {
|
|||||||
animation: none;
|
animation: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#auth,
|
|
||||||
#dashboard {
|
#dashboard {
|
||||||
display: none;
|
|
||||||
-webkit-animation: fadeInOpacity .5s;
|
-webkit-animation: fadeInOpacity .5s;
|
||||||
animation: fadeInOpacity .5s;
|
animation: fadeInOpacity .5s;
|
||||||
}
|
}
|
||||||
@ -102,15 +100,11 @@ li[data-action="page-ellipsis"] {
|
|||||||
|
|
||||||
.no-touch .image-container .checkbox {
|
.no-touch .image-container .checkbox {
|
||||||
opacity: .5;
|
opacity: .5;
|
||||||
-webkit-transition: opacity .25s;
|
|
||||||
transition: opacity .25s;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.no-touch .image-container .controls,
|
.no-touch .image-container .controls,
|
||||||
.no-touch .image-container .details {
|
.no-touch .image-container .details {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
-webkit-transition: opacity .25s;
|
|
||||||
transition: opacity .25s;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.no-touch .image-container:hover .checkbox,
|
.no-touch .image-container:hover .checkbox,
|
||||||
|
@ -24,6 +24,9 @@ const page = {
|
|||||||
username: null,
|
username: null,
|
||||||
permissions: null,
|
permissions: null,
|
||||||
|
|
||||||
|
// sidebar menus
|
||||||
|
menus: [],
|
||||||
|
|
||||||
currentView: null,
|
currentView: null,
|
||||||
views: {
|
views: {
|
||||||
// config of uploads view
|
// config of uploads view
|
||||||
@ -75,7 +78,8 @@ const page = {
|
|||||||
clipboardJS: null,
|
clipboardJS: null,
|
||||||
lazyLoad: 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
|
fadingIn: null
|
||||||
}
|
}
|
||||||
@ -115,77 +119,64 @@ page.verifyToken = function (token, reloadOnError) {
|
|||||||
|
|
||||||
page.prepareDashboard = function () {
|
page.prepareDashboard = function () {
|
||||||
page.dom = document.querySelector('#page')
|
page.dom = document.querySelector('#page')
|
||||||
|
|
||||||
|
// Capture all click events
|
||||||
page.dom.addEventListener('click', page.domClick, true)
|
page.dom.addEventListener('click', page.domClick, true)
|
||||||
|
|
||||||
|
// Capture all submit events
|
||||||
page.dom.addEventListener('submit', function (event) {
|
page.dom.addEventListener('submit', function (event) {
|
||||||
const element = event.target
|
// Prevent default if necessary
|
||||||
if (element && element.classList.contains('prevent-default'))
|
if (event.target && event.target.classList.contains('prevent-default'))
|
||||||
return event.preventDefault()
|
return event.preventDefault()
|
||||||
}, true)
|
}, 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) {
|
if (page.permissions.moderator) {
|
||||||
document.querySelector('#itemLabelAdmin').style.display = 'block'
|
document.querySelector('#itemLabelAdmin').classList.remove('is-hidden')
|
||||||
document.querySelector('#itemListAdmin').style.display = 'block'
|
document.querySelector('#itemListAdmin').classList.remove('is-hidden')
|
||||||
const itemManageUploads = document.querySelector('#itemManageUploads')
|
|
||||||
itemManageUploads.addEventListener('click', function () {
|
|
||||||
page.setActiveMenu(this)
|
|
||||||
page.getUploads({ all: true })
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (page.permissions.admin) {
|
// Update text of logout button
|
||||||
const itemServerStats = document.querySelector('#itemServerStats')
|
document.querySelector('#itemLogout').innerHTML = `Logout ( ${page.username} )`
|
||||||
itemServerStats.addEventListener('click', function () {
|
|
||||||
page.setActiveMenu(this)
|
|
||||||
page.getServerStats()
|
|
||||||
})
|
|
||||||
|
|
||||||
const itemManageUsers = document.querySelector('#itemManageUsers')
|
// Finally display dashboard
|
||||||
itemManageUsers.addEventListener('click', function () {
|
document.querySelector('#dashboard').classList.remove('is-hidden')
|
||||||
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} )`
|
|
||||||
|
|
||||||
|
// Load albums sidebar
|
||||||
page.getAlbumsSidebar()
|
page.getAlbumsSidebar()
|
||||||
|
|
||||||
if (typeof page.prepareShareX === 'function') page.prepareShareX()
|
if (typeof page.prepareShareX === 'function')
|
||||||
|
page.prepareShareX()
|
||||||
}
|
}
|
||||||
|
|
||||||
page.logout = function () {
|
page.logout = function () {
|
||||||
@ -234,22 +225,20 @@ page.domClick = function (event) {
|
|||||||
return page.setUploadsView('thumbs', element)
|
return page.setUploadsView('thumbs', element)
|
||||||
case 'clear-selection':
|
case 'clear-selection':
|
||||||
return page.clearSelection()
|
return page.clearSelection()
|
||||||
case 'add-selected-files-to-album':
|
case 'add-selected-uploads-to-album':
|
||||||
return page.addSelectedFilesToAlbum()
|
return page.addSelectedUploadsToAlbum()
|
||||||
case 'bulk-delete':
|
|
||||||
return page.deleteSelectedFiles()
|
|
||||||
case 'select':
|
case 'select':
|
||||||
return page.select(element, event)
|
return page.select(element, event)
|
||||||
case 'add-to-album':
|
|
||||||
return page.addSingleFileToAlbum(id)
|
|
||||||
case 'delete-file':
|
|
||||||
return page.deleteFile(id)
|
|
||||||
case 'select-all':
|
case 'select-all':
|
||||||
return page.selectAll(element)
|
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':
|
case 'display-thumbnail':
|
||||||
return page.displayThumbnail(id)
|
return page.displayThumbnail(id)
|
||||||
case 'delete-file-by-names':
|
|
||||||
return page.deleteFileByNames()
|
|
||||||
case 'submit-album':
|
case 'submit-album':
|
||||||
return page.submitAlbum(element)
|
return page.submitAlbum(element)
|
||||||
case 'edit-album':
|
case 'edit-album':
|
||||||
@ -457,12 +446,12 @@ page.getUploads = function ({ pageNum, album, all, filters, autoPage } = {}, ele
|
|||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
${all ? '' : `
|
${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">
|
<span class="icon">
|
||||||
<i class="icon-plus"></i>
|
<i class="icon-plus"></i>
|
||||||
</span>
|
</span>
|
||||||
</a>`}
|
</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">
|
<span class="icon">
|
||||||
<i class="icon-trash"></i>
|
<i class="icon-trash"></i>
|
||||||
</span>
|
</span>
|
||||||
@ -472,8 +461,8 @@ page.getUploads = function ({ pageNum, album, all, filters, autoPage } = {}, ele
|
|||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
|
|
||||||
// Set to true to tick "all files" checkbox in list view
|
// Whether there are any unselected items
|
||||||
let allSelected = true
|
let unselected = false
|
||||||
|
|
||||||
const hasExpiryDateColumn = files.some(file => file.expirydate !== undefined)
|
const hasExpiryDateColumn = files.some(file => file.expirydate !== undefined)
|
||||||
|
|
||||||
@ -501,7 +490,7 @@ page.getUploads = function ({ pageNum, album, all, filters, autoPage } = {}, ele
|
|||||||
|
|
||||||
// Update selected status
|
// Update selected status
|
||||||
files[i].selected = page.selected[page.currentView].includes(files[i].id)
|
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)
|
// Appendix (display album or user)
|
||||||
if (all)
|
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 = `<a class="image" href="${upload.file}" target="_blank" rel="noopener"><h1 class="title">${upload.extname || 'N/A'}</h1></a>`
|
||||||
|
|
||||||
div.innerHTML += `
|
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">
|
<div class="controls">
|
||||||
<a class="button is-small is-primary" title="View thumbnail" data-action="display-thumbnail"${upload.thumb ? '' : ' disabled'}>
|
<a class="button is-small is-primary" title="View thumbnail" data-action="display-thumbnail"${upload.thumb ? '' : ' disabled'}>
|
||||||
<span class="icon">
|
<span class="icon">
|
||||||
@ -556,7 +545,7 @@ page.getUploads = function ({ pageNum, album, all, filters, autoPage } = {}, ele
|
|||||||
<i class="icon-plus"></i>
|
<i class="icon-plus"></i>
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</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">
|
<span class="icon">
|
||||||
<i class="icon-trash"></i>
|
<i class="icon-trash"></i>
|
||||||
</span>
|
</span>
|
||||||
@ -581,7 +570,7 @@ page.getUploads = function ({ pageNum, album, all, filters, autoPage } = {}, ele
|
|||||||
<table class="table is-narrow is-fullwidth is-hoverable">
|
<table class="table is-narrow is-fullwidth is-hoverable">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<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 style="width: 20%">File</th>
|
||||||
<th>${all ? 'User' : 'Album'}</th>
|
<th>${all ? 'User' : 'Album'}</th>
|
||||||
<th>Size</th>
|
<th>Size</th>
|
||||||
@ -606,7 +595,7 @@ page.getUploads = function ({ pageNum, album, all, filters, autoPage } = {}, ele
|
|||||||
const tr = document.createElement('tr')
|
const tr = document.createElement('tr')
|
||||||
tr.dataset.id = upload.id
|
tr.dataset.id = upload.id
|
||||||
tr.innerHTML = `
|
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><a href="${upload.file}" target="_blank" rel="noopener" title="${upload.file}">${upload.name}</a></th>
|
||||||
<th>${upload.appendix}</th>
|
<th>${upload.appendix}</th>
|
||||||
<td>${upload.prettyBytes}</td>
|
<td>${upload.prettyBytes}</td>
|
||||||
@ -630,7 +619,7 @@ page.getUploads = function ({ pageNum, album, all, filters, autoPage } = {}, ele
|
|||||||
<i class="icon-plus"></i>
|
<i class="icon-plus"></i>
|
||||||
</span>
|
</span>
|
||||||
</a>`}
|
</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">
|
<span class="icon">
|
||||||
<i class="icon-trash"></i>
|
<i class="icon-trash"></i>
|
||||||
</span>
|
</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.checkboxes[page.currentView] = Array.from(table.querySelectorAll('.checkbox[data-action="select"]'))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
page.fadeAndScroll()
|
|
||||||
|
|
||||||
if (allSelected && files.length) {
|
const selectAll = document.querySelector('#selectAll')
|
||||||
const selectAll = document.querySelector('#selectAll')
|
if (selectAll && !unselected) {
|
||||||
if (selectAll) selectAll.checked = true
|
selectAll.checked = true
|
||||||
|
selectAll.title = 'Unselect all'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
page.fadeAndScroll()
|
||||||
|
|
||||||
if (page.currentView === 'uploads') page.views.uploads.album = album
|
if (page.currentView === 'uploads') page.views.uploads.album = album
|
||||||
if (page.currentView === 'uploadsAll') page.views.uploadsAll.filters = filters
|
if (page.currentView === 'uploadsAll') page.views.uploadsAll.filters = filters
|
||||||
page.views[page.currentView].pageNum = files.length ? pageNum : 0
|
page.views[page.currentView].pageNum = files.length ? pageNum : 0
|
||||||
@ -693,27 +684,28 @@ page.displayThumbnail = function (id) {
|
|||||||
|
|
||||||
const thumb = div.querySelector('#swalThumb')
|
const thumb = div.querySelector('#swalThumb')
|
||||||
const exec = /.[\w]+(\?|$)/.exec(original)
|
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.src = file.original
|
||||||
thumb.onload = function () {
|
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')
|
document.body.querySelector('.swal-overlay .swal-modal:not(.is-expanded)').classList.add('is-expanded')
|
||||||
}
|
}
|
||||||
thumb.onerror = function () {
|
thumb.onerror = function () {
|
||||||
button.className = 'button is-danger'
|
button.className = 'button is-danger'
|
||||||
button.innerHTML = 'Unable to load original'
|
button.innerHTML = 'Unable to load original'
|
||||||
}
|
}
|
||||||
} else {
|
} else if (page.videoExts.includes(extname)) {
|
||||||
thumb.style.display = 'none'
|
thumb.classList.add('is-hidden')
|
||||||
const video = document.createElement('video')
|
const video = document.createElement('video')
|
||||||
video.id = 'swalVideo'
|
video.id = 'swalVideo'
|
||||||
video.controls = true
|
video.controls = true
|
||||||
video.src = file.original
|
video.src = file.original
|
||||||
thumb.insertAdjacentElement('afterend', video)
|
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')
|
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) {
|
page.selectAll = function (element) {
|
||||||
const checkboxes = page.checkboxes[page.currentView]
|
for (let i = 0; i < page.checkboxes[page.currentView].length; i++) {
|
||||||
const selected = page.selected[page.currentView]
|
const id = page.getItemID(page.checkboxes[page.currentView][i])
|
||||||
|
|
||||||
for (let i = 0; i < checkboxes.length; i++) {
|
|
||||||
const id = page.getItemID(checkboxes[i])
|
|
||||||
if (isNaN(id)) continue
|
if (isNaN(id)) continue
|
||||||
if (checkboxes[i].checked !== element.checked) {
|
if (page.checkboxes[page.currentView][i].checked !== element.checked) {
|
||||||
checkboxes[i].checked = element.checked
|
page.checkboxes[page.currentView][i].checked = element.checked
|
||||||
if (checkboxes[i].checked)
|
if (page.checkboxes[page.currentView][i].checked)
|
||||||
selected.push(id)
|
page.selected[page.currentView].push(id)
|
||||||
else
|
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)
|
if (page.selected[page.currentView].length)
|
||||||
page.selected[page.currentView] = selected
|
localStorage[lsKeys.selected[page.currentView]] = JSON.stringify(page.selected[page.currentView])
|
||||||
element.title = element.checked ? 'Unselect all uploads' : 'Select all uploads'
|
else
|
||||||
|
delete localStorage[lsKeys.selected[page.currentView]]
|
||||||
|
|
||||||
|
element.title = element.checked ? 'Unselect all' : 'Select all'
|
||||||
}
|
}
|
||||||
|
|
||||||
page.selectInBetween = function (element, lastElement) {
|
page.selectInBetween = function (element, lastElement) {
|
||||||
if (!element || !lastElement) return
|
if (!element || !lastElement || element === lastElement)
|
||||||
if (element === lastElement) return
|
return
|
||||||
|
|
||||||
const checkboxes = page.checkboxes[page.currentView]
|
if (!Array.isArray(page.checkboxes[page.currentView]) || !page.checkboxes[page.currentView].length)
|
||||||
if (!checkboxes || !checkboxes.length) return
|
return
|
||||||
|
|
||||||
const thisIndex = checkboxes.indexOf(element)
|
const thisIndex = page.checkboxes[page.currentView].indexOf(element)
|
||||||
const lastIndex = checkboxes.indexOf(lastElement)
|
const lastIndex = page.checkboxes[page.currentView].indexOf(lastElement)
|
||||||
|
|
||||||
const distance = thisIndex - lastIndex
|
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) ||
|
if ((thisIndex > lastIndex && i > lastIndex && i < thisIndex) ||
|
||||||
(thisIndex < lastIndex && i > thisIndex && i < lastIndex)) {
|
(thisIndex < lastIndex && i > thisIndex && i < lastIndex)) {
|
||||||
checkboxes[i].checked = true
|
// Check or uncheck depending on the state of the initial checkbox
|
||||||
page.selected[page.currentView].push(page.getItemID(checkboxes[i]))
|
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) {
|
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)
|
const id = page.getItemID(element)
|
||||||
if (isNaN(id)) return
|
if (isNaN(id)) return
|
||||||
|
|
||||||
const selected = page.selected[page.currentView]
|
const lastSelected = page.lastSelected[page.currentView]
|
||||||
if (!selected.includes(id) && element.checked)
|
if (event.shiftKey && lastSelected) {
|
||||||
selected.push(id)
|
page.selectInBetween(element, lastSelected)
|
||||||
else if (selected.includes(id) && !element.checked)
|
// Check or uncheck depending on the state of the initial checkbox
|
||||||
selected.splice(selected.indexOf(id), 1)
|
element.checked = lastSelected.checked
|
||||||
|
} else {
|
||||||
|
page.lastSelected[page.currentView] = element
|
||||||
|
}
|
||||||
|
|
||||||
localStorage[lsKeys.selected[page.currentView]] = JSON.stringify(selected)
|
if (!page.selected[page.currentView].includes(id) && element.checked)
|
||||||
page.selected[page.currentView] = selected
|
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 () {
|
page.clearSelection = function () {
|
||||||
@ -830,7 +830,7 @@ page.filtersHelp = function (element) {
|
|||||||
const content = document.createElement('div')
|
const content = document.createElement('div')
|
||||||
content.style = 'text-align: left'
|
content.style = 'text-align: left'
|
||||||
content.innerHTML = `
|
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.
|
Each key can be specified more than once.
|
||||||
Backlashes should be used if the usernames have spaces.
|
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.
|
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":
|
Uploads from users with username either "John Doe" OR "demo":
|
||||||
<code>user:John\\ Doe user:demo</code>
|
<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>
|
<code>ip:127.0.0.1 name:*.rar name:*.zip</code>
|
||||||
|
|
||||||
Uploads from user with username "test" OR from non-registered users:
|
Uploads from user with username "test" OR from non-registered users:
|
||||||
@ -864,56 +864,133 @@ page.filterUploads = function (element) {
|
|||||||
page.viewUserUploads = function (id) {
|
page.viewUserUploads = function (id) {
|
||||||
const user = page.cache.users[id]
|
const user = page.cache.users[id]
|
||||||
if (!user) return
|
if (!user) return
|
||||||
page.setActiveMenu(document.querySelector('#itemManageUploads'))
|
|
||||||
page.getUploads({ all: true, filters: `user:${user.username.replace(/ /g, '\\ ')}` })
|
page.getUploads({ all: true, filters: `user:${user.username.replace(/ /g, '\\ ')}` })
|
||||||
|
page.setActiveMenu(document.querySelector('#itemManageUploads'))
|
||||||
}
|
}
|
||||||
|
|
||||||
page.deleteFile = function (id) {
|
page.deleteUpload = function (id) {
|
||||||
// TODO: Share function with bulk delete, just like 'add selected uploads to album' and 'add single file to album'
|
page.postBulkDeleteUploads({
|
||||||
swal({
|
field: 'id',
|
||||||
title: 'Are you sure?',
|
values: [id],
|
||||||
text: 'You won\'t be able to recover the file!',
|
cb (failed) {
|
||||||
icon: 'warning',
|
// Remove from remembered checkboxes if necessary
|
||||||
dangerMode: true,
|
if (!failed.length && page.selected[page.currentView].includes(id))
|
||||||
buttons: {
|
page.selected[page.currentView].splice(page.selected[page.currentView].indexOf(id), 1)
|
||||||
cancel: true,
|
|
||||||
confirm: {
|
|
||||||
text: 'Yes, delete it!',
|
|
||||||
closeModal: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}).then(function (proceed) {
|
|
||||||
if (!proceed) return
|
|
||||||
|
|
||||||
axios.post('api/upload/delete', { id }).then(function (response) {
|
// Update local storage
|
||||||
if (!response) return
|
if (page.selected[page.currentView].length)
|
||||||
|
localStorage[lsKeys.selected[page.currentView]] = JSON.stringify(page.selected[page.currentView])
|
||||||
if (response.data.success === false)
|
else
|
||||||
if (response.data.description === 'No token provided') {
|
delete localStorage[lsKeys.selected[page.currentView]]
|
||||||
return page.verifyToken(page.token)
|
|
||||||
} else {
|
|
||||||
return swal('An error occurred!', response.data.description, 'error')
|
|
||||||
}
|
|
||||||
|
|
||||||
swal('Deleted!', 'The file has been deleted.', 'success')
|
|
||||||
|
|
||||||
|
// Reload upload list
|
||||||
const views = Object.assign({}, page.views[page.currentView])
|
const views = Object.assign({}, page.views[page.currentView])
|
||||||
views.autoPage = true
|
views.autoPage = true
|
||||||
page.getUploads(views)
|
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
|
const count = page.selected[page.currentView].length
|
||||||
if (!count)
|
if (!count)
|
||||||
return swal('An error occurred!', 'You have not selected any uploads.', 'error')
|
return swal('An error occurred!', 'You have not selected any uploads.', 'error')
|
||||||
|
|
||||||
const suffix = `upload${count === 1 ? '' : 's'}`
|
page.postBulkDeleteUploads({
|
||||||
let text = `You won't be able to recover ${count} ${suffix}!`
|
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')
|
if (page.currentView === 'uploadsAll')
|
||||||
text += '\nBe aware, you may be nuking uploads by other users!'
|
text += '\nBe aware, you may be nuking uploads by other users!'
|
||||||
|
|
||||||
@ -925,41 +1002,32 @@ page.deleteSelectedFiles = function () {
|
|||||||
buttons: {
|
buttons: {
|
||||||
cancel: true,
|
cancel: true,
|
||||||
confirm: {
|
confirm: {
|
||||||
text: `Yes, nuke the ${suffix}!`,
|
text: `Yes, nuke ${values.length === 1 ? 'it' : 'them'}!`,
|
||||||
closeModal: false
|
closeModal: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).then(function (proceed) {
|
}).then(function (proceed) {
|
||||||
if (!proceed) return
|
if (!proceed) return
|
||||||
|
|
||||||
axios.post('api/upload/bulkdelete', {
|
axios.post('api/upload/bulkdelete', { field, values }).then(function (response) {
|
||||||
field: 'id',
|
if (!response) return
|
||||||
values: page.selected[page.currentView]
|
|
||||||
}).then(function (bulkdelete) {
|
|
||||||
if (!bulkdelete) return
|
|
||||||
|
|
||||||
if (bulkdelete.data.success === false)
|
if (response.data.success === false)
|
||||||
if (bulkdelete.data.description === 'No token provided') {
|
if (response.data.description === 'No token provided') {
|
||||||
return page.verifyToken(page.token)
|
return page.verifyToken(page.token)
|
||||||
} else {
|
} 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) {
|
const failed = Array.isArray(response.data.failed) ? response.data.failed : []
|
||||||
page.selected[page.currentView] = page.selected[page.currentView].filter(function (id) {
|
if (failed.length === values.length)
|
||||||
return bulkdelete.data.failed.includes(id)
|
swal('An error occurred!', `Unable to delete any of the ${objective}.`, 'error')
|
||||||
})
|
else if (failed.length && failed.length < values.length)
|
||||||
localStorage[lsKeys.selected[page.currentView]] = JSON.stringify(page.selected[page.currentView])
|
swal('Warning!', `From ${objective}, unable to delete ${failed.length} of them.`, 'warning')
|
||||||
swal('An error ocurrred!', `From ${count} ${suffix}, unable to delete ${bulkdelete.data.failed.length} of them.`, 'error')
|
else
|
||||||
} else {
|
swal('Deleted!', `${objective} ${count === 1 ? 'has' : 'have'} been deleted.`, 'success')
|
||||||
page.selected[page.currentView] = []
|
|
||||||
delete localStorage[lsKeys.selected[page.currentView]]
|
|
||||||
swal('Deleted!', `${count} ${suffix} ${count === 1 ? 'has' : 'have'} been deleted.`, 'success')
|
|
||||||
}
|
|
||||||
|
|
||||||
const views = Object.assign({}, page.views[page.currentView])
|
if (typeof cb === 'function') cb(failed)
|
||||||
views.autoPage = true
|
|
||||||
page.getUploads(views)
|
|
||||||
}).catch(function (error) {
|
}).catch(function (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
swal('An error occurred!', 'There was an error with the request, please check the console for more information.', '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 () {
|
page.addSelectedUploadsToAlbum = 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 () {
|
|
||||||
if (page.currentView !== 'uploads')
|
if (page.currentView !== 'uploads')
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -1062,7 +1043,7 @@ page.addSelectedFilesToAlbum = function () {
|
|||||||
if (!count)
|
if (!count)
|
||||||
return swal('An error occurred!', 'You have not selected any uploads.', 'error')
|
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) return
|
||||||
if (failed.length)
|
if (failed.length)
|
||||||
page.selected[page.currentView] = page.selected[page.currentView].filter(function (id) {
|
page.selected[page.currentView] = page.selected[page.currentView].filter(function (id) {
|
||||||
@ -1076,21 +1057,21 @@ page.addSelectedFilesToAlbum = function () {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
page.addSingleFileToAlbum = function (id) {
|
page.addToAlbum = function (id) {
|
||||||
page.addFilesToAlbum([id], function (failed) {
|
page.addUploadsToAlbum([id], function (failed) {
|
||||||
if (!failed) return
|
if (!failed) return
|
||||||
page.getUploads(page.views[page.currentView])
|
page.getUploads(page.views[page.currentView])
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
page.addFilesToAlbum = function (ids, callback) {
|
page.addUploadsToAlbum = function (ids, callback) {
|
||||||
const count = ids.length
|
const count = ids.length
|
||||||
|
|
||||||
const content = document.createElement('div')
|
const content = document.createElement('div')
|
||||||
content.innerHTML = `
|
content.innerHTML = `
|
||||||
<div class="field has-text-centered">
|
<div class="field has-text-centered">
|
||||||
<p>You are about to add <b>${count}</b> file${count === 1 ? '' : 's'} to an album.</p>
|
<p>You are about to add <b>${count}</b> upload${count === 1 ? '' : 's'} to an album.</p>
|
||||||
<p><b>If a file is already in an album, it will be moved.</b></p>
|
<p><b>If an upload is already in an album, it will be moved.</b></p>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="control">
|
<div class="control">
|
||||||
@ -1140,7 +1121,7 @@ page.addFilesToAlbum = function (ids, callback) {
|
|||||||
if (add.data.failed && add.data.failed.length)
|
if (add.data.failed && add.data.failed.length)
|
||||||
added -= 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)
|
if (!added)
|
||||||
return swal('An error occurred!', `Could not add the ${suffix} to the album.`, 'error')
|
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')
|
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++) {
|
for (let i = 0; i < response.data.albums.length; i++) {
|
||||||
const album = response.data.albums[i]
|
const album = response.data.albums[i]
|
||||||
@ -1481,8 +1470,10 @@ page.getAlbumsSidebar = function () {
|
|||||||
a.innerHTML = album.name
|
a.innerHTML = album.name
|
||||||
|
|
||||||
a.addEventListener('click', function () {
|
a.addEventListener('click', function () {
|
||||||
page.getAlbum(this)
|
page.getUploads({ album: this.id })
|
||||||
|
page.setActiveMenu(this)
|
||||||
})
|
})
|
||||||
|
page.menus.push(a)
|
||||||
|
|
||||||
li.appendChild(a)
|
li.appendChild(a)
|
||||||
albumsContainer.appendChild(li)
|
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 () {
|
page.changeToken = function () {
|
||||||
axios.get('api/tokens').then(function (response) {
|
axios.get('api/tokens').then(function (response) {
|
||||||
if (response.data.success === false)
|
if (response.data.success === false)
|
||||||
@ -1508,7 +1494,6 @@ page.changeToken = function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
page.dom.innerHTML = `
|
page.dom.innerHTML = `
|
||||||
<h2 class="subtitle">Manage your token</h2>
|
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label">Your current token:</label>
|
<label class="label">Your current token:</label>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
@ -1567,7 +1552,6 @@ page.getNewToken = function (element) {
|
|||||||
|
|
||||||
page.changePassword = function () {
|
page.changePassword = function () {
|
||||||
page.dom.innerHTML = `
|
page.dom.innerHTML = `
|
||||||
<h2 class="subtitle">Change your password</h2>
|
|
||||||
<form class="prevent-default">
|
<form class="prevent-default">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label">New password:</label>
|
<label class="label">New password:</label>
|
||||||
@ -1634,13 +1618,11 @@ page.sendNewPassword = function (pass, element) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
page.setActiveMenu = function (activeItem) {
|
page.setActiveMenu = function (element) {
|
||||||
const menu = document.querySelector('#menu')
|
for (let i = 0; i < page.menus.length; i++)
|
||||||
const items = menu.getElementsByTagName('a')
|
page.menus[i].classList.remove('is-active')
|
||||||
for (let i = 0; i < items.length; i++)
|
|
||||||
items[i].classList.remove('is-active')
|
|
||||||
|
|
||||||
activeItem.classList.add('is-active')
|
element.classList.add('is-active')
|
||||||
}
|
}
|
||||||
|
|
||||||
page.getUsers = function ({ pageNum } = {}, element) {
|
page.getUsers = function ({ pageNum } = {}, element) {
|
||||||
@ -1717,7 +1699,8 @@ page.getUsers = function ({ pageNum } = {}, element) {
|
|||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
|
|
||||||
let allSelected = true
|
// Whether there are any unselected items
|
||||||
|
let unselected = false
|
||||||
|
|
||||||
page.dom.innerHTML = `
|
page.dom.innerHTML = `
|
||||||
${pagination}
|
${pagination}
|
||||||
@ -1727,7 +1710,7 @@ page.getUsers = function ({ pageNum } = {}, element) {
|
|||||||
<table class="table is-narrow is-fullwidth is-hoverable">
|
<table class="table is-narrow is-fullwidth is-hoverable">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<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>ID</th>
|
||||||
<th style="width: 20%">Username</th>
|
<th style="width: 20%">Username</th>
|
||||||
<th>Uploads</th>
|
<th>Uploads</th>
|
||||||
@ -1749,7 +1732,7 @@ page.getUsers = function ({ pageNum } = {}, element) {
|
|||||||
for (let i = 0; i < response.data.users.length; i++) {
|
for (let i = 0; i < response.data.users.length; i++) {
|
||||||
const user = response.data.users[i]
|
const user = response.data.users[i]
|
||||||
const selected = page.selected.users.includes(user.id)
|
const selected = page.selected.users.includes(user.id)
|
||||||
if (!selected && allSelected) allSelected = false
|
if (!selected) unselected = true
|
||||||
|
|
||||||
let displayGroup = null
|
let displayGroup = null
|
||||||
const groups = Object.keys(user.groups)
|
const groups = Object.keys(user.groups)
|
||||||
@ -1770,11 +1753,11 @@ page.getUsers = function ({ pageNum } = {}, element) {
|
|||||||
const tr = document.createElement('tr')
|
const tr = document.createElement('tr')
|
||||||
tr.dataset.id = user.id
|
tr.dataset.id = user.id
|
||||||
tr.innerHTML = `
|
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>${user.id}</th>
|
||||||
<th${enabled ? '' : ' class="is-linethrough"'}>${user.username}</td>
|
<th${enabled ? '' : ' class="is-linethrough"'}>${user.username}</td>
|
||||||
<th>${user.uploadsCount}</th>
|
<th>${user.uploads}</th>
|
||||||
<td>${page.getPrettyBytes(user.diskUsage)}</td>
|
<td>${page.getPrettyBytes(user.usage)}</td>
|
||||||
<td>${displayGroup}</td>
|
<td>${displayGroup}</td>
|
||||||
<td class="controls" style="text-align: right">
|
<td class="controls" style="text-align: right">
|
||||||
<a class="button is-small is-primary" title="Edit user" data-action="edit-user">
|
<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)
|
table.appendChild(tr)
|
||||||
page.checkboxes.users = Array.from(table.querySelectorAll('.checkbox[data-action="select"]'))
|
page.checkboxes.users = Array.from(table.querySelectorAll('.checkbox[data-action="select"]'))
|
||||||
}
|
}
|
||||||
page.fadeAndScroll()
|
|
||||||
|
|
||||||
if (allSelected && response.data.users.length) {
|
const selectAll = document.querySelector('#selectAll')
|
||||||
const selectAll = document.querySelector('#selectAll')
|
if (selectAll && !unselected) {
|
||||||
if (selectAll) selectAll.checked = true
|
selectAll.checked = true
|
||||||
|
selectAll.title = 'Unselect all'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
page.fadeAndScroll()
|
||||||
|
|
||||||
page.views.users.pageNum = response.data.users.length ? pageNum : 0
|
page.views.users.pageNum = response.data.users.length ? pageNum : 0
|
||||||
}).catch(function (error) {
|
}).catch(function (error) {
|
||||||
if (element) page.isLoading(element, false)
|
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)
|
if (!page.permissions.admin)
|
||||||
return swal('An error occurred!', 'You can not do this!', 'error')
|
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
|
Please wait, this may take a while\u2026
|
||||||
<progress class="progress is-breeze" max="100" style="margin-top: 10px"></progress>
|
<progress class="progress is-breeze" max="100" style="margin-top: 10px"></progress>
|
||||||
`
|
`
|
||||||
page.fadeAndScroll()
|
|
||||||
|
|
||||||
const url = 'api/stats'
|
const url = 'api/stats'
|
||||||
axios.get(url).then(function (response) {
|
axios.get(url).then(function (response) {
|
||||||
@ -2040,20 +2024,31 @@ page.getServerStats = function (element) {
|
|||||||
`
|
`
|
||||||
else
|
else
|
||||||
try {
|
try {
|
||||||
|
const types = response.data.stats[keys[i]]._types || {}
|
||||||
const valKeys = Object.keys(response.data.stats[keys[i]])
|
const valKeys = Object.keys(response.data.stats[keys[i]])
|
||||||
for (let j = 0; j < valKeys.length; j++) {
|
for (let j = 0; j < valKeys.length; j++) {
|
||||||
const _value = response.data.stats[keys[i]][valKeys[j]]
|
// Skip keys that starts with an underscore
|
||||||
let value = _value
|
if (/^_/.test(valKeys[j]))
|
||||||
if (['albums', 'users', 'uploads'].includes(keys[i]))
|
continue
|
||||||
value = _value.toLocaleString()
|
|
||||||
if (['memoryUsage', 'size'].includes(valKeys[j]))
|
const value = response.data.stats[keys[i]][valKeys[j]]
|
||||||
value = page.getPrettyBytes(_value)
|
let parsed = value
|
||||||
if (valKeys[j] === 'systemMemory')
|
|
||||||
value = `${page.getPrettyBytes(_value.used)} / ${page.getPrettyBytes(_value.total)} (${Math.round(_value.used / _value.total * 100)}%)`
|
// 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 += `
|
rows += `
|
||||||
<tr>
|
<tr>
|
||||||
<th>${valKeys[j].replace(/([A-Z])/g, ' $1').toUpperCase()}</th>
|
<th>${string}</th>
|
||||||
<td>${value}</td>
|
<td>${parsed}</td>
|
||||||
</tr>
|
</tr>
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
@ -2084,11 +2079,16 @@ page.getServerStats = function (element) {
|
|||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
page.dom.innerHTML = `
|
page.dom.innerHTML = content
|
||||||
<h2 class="subtitle">Statistics</h2>
|
|
||||||
${content}
|
|
||||||
`
|
|
||||||
page.fadeAndScroll()
|
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()
|
page.preparePage()
|
||||||
}).catch(function (error) {
|
}).catch(function (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
document.querySelector('#albumDiv').style.display = 'none'
|
document.querySelector('#albumDiv').classList.add('is-hidden')
|
||||||
document.querySelector('#tabs').style.display = 'none'
|
document.querySelector('#tabs').classList.add('is-hidden')
|
||||||
const button = document.querySelector('#loginToUpload')
|
const button = document.querySelector('#loginToUpload')
|
||||||
button.classList.remove('is-loading')
|
|
||||||
button.innerText = 'Error occurred. Reload the page?'
|
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')
|
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')
|
button.classList.remove('is-loading')
|
||||||
|
|
||||||
if (page.enableUserAccounts)
|
if (page.enableUserAccounts)
|
||||||
button.innerText = 'Anonymous upload is disabled. Log in to page.'
|
button.innerText = 'Anonymous upload is disabled. Log in to upload.'
|
||||||
else
|
else
|
||||||
button.innerText = 'Running in private mode. Log in to page.'
|
button.innerText = 'Running in private mode. Log in to upload.'
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
return page.prepareUpload()
|
return page.prepareUpload()
|
||||||
@ -120,13 +121,13 @@ page.prepareUpload = function () {
|
|||||||
page.prepareAlbums()
|
page.prepareAlbums()
|
||||||
|
|
||||||
// Display the album selection
|
// Display the album selection
|
||||||
document.querySelector('#albumDiv').style.display = 'flex'
|
document.querySelector('#albumDiv').classList.remove('is-hidden')
|
||||||
}
|
}
|
||||||
|
|
||||||
page.prepareUploadConfig()
|
page.prepareUploadConfig()
|
||||||
|
|
||||||
document.querySelector('#maxSize').innerHTML = `Maximum upload size per file is ${page.getPrettyBytes(page.maxSizeBytes)}`
|
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)
|
if (!page.token && page.enableUserAccounts)
|
||||||
document.querySelector('#loginLinkText').innerHTML = 'Create an account and keep track of your uploads'
|
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(this.dataset.id)
|
||||||
})
|
})
|
||||||
page.setActiveTab('tab-files')
|
page.setActiveTab('tab-files')
|
||||||
tabs.style.display = 'flex'
|
tabs.classList.remove('is-hidden')
|
||||||
}
|
}
|
||||||
|
|
||||||
page.prepareAlbums = function () {
|
page.prepareAlbums = function () {
|
||||||
@ -203,10 +204,10 @@ page.setActiveTab = function (tabId) {
|
|||||||
const id = page.tabs[i].dataset.id
|
const id = page.tabs[i].dataset.id
|
||||||
if (id === tabId) {
|
if (id === tabId) {
|
||||||
page.tabs[i].classList.add('is-active')
|
page.tabs[i].classList.add('is-active')
|
||||||
document.querySelector(`#${id}`).style.display = 'block'
|
document.querySelector(`#${id}`).classList.remove('is-hidden')
|
||||||
} else {
|
} else {
|
||||||
page.tabs[i].classList.remove('is-active')
|
page.tabs[i].classList.remove('is-active')
|
||||||
document.querySelector(`#${id}`).style.display = 'none'
|
document.querySelector(`#${id}`).classList.add('is-hidden')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
page.activeTab = tabId
|
page.activeTab = tabId
|
||||||
@ -268,7 +269,7 @@ page.prepareDropzone = function () {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).then(function (response) {
|
}).then(function (response) {
|
||||||
file.previewElement.querySelector('.progress').style.display = 'none'
|
file.previewElement.querySelector('.progress').classList.add('is-hidden')
|
||||||
|
|
||||||
if (response.data.success === false)
|
if (response.data.success === false)
|
||||||
file.previewElement.querySelector('.error').innerHTML = response.data.description
|
file.previewElement.querySelector('.error').innerHTML = response.data.description
|
||||||
@ -285,7 +286,7 @@ page.prepareDropzone = function () {
|
|||||||
// Set active tab to file uploads
|
// Set active tab to file uploads
|
||||||
page.setActiveTab('tab-files')
|
page.setActiveTab('tab-files')
|
||||||
// Add file entry
|
// Add file entry
|
||||||
tabDiv.querySelector('.uploads').style.display = 'block'
|
tabDiv.querySelector('.uploads').classList.remove('is-hidden')
|
||||||
file.previewElement.querySelector('.name').innerHTML = file.name
|
file.previewElement.querySelector('.name').innerHTML = file.name
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -308,7 +309,7 @@ page.prepareDropzone = function () {
|
|||||||
|
|
||||||
page.dropzone.on('success', function (file, response) {
|
page.dropzone.on('success', function (file, response) {
|
||||||
if (!response) return
|
if (!response) return
|
||||||
file.previewElement.querySelector('.progress').style.display = 'none'
|
file.previewElement.querySelector('.progress').classList.add('is-hidden')
|
||||||
|
|
||||||
if (response.success === false)
|
if (response.success === false)
|
||||||
file.previewElement.querySelector('.error').innerHTML = response.description
|
file.previewElement.querySelector('.error').innerHTML = response.description
|
||||||
@ -324,7 +325,7 @@ page.prepareDropzone = function () {
|
|||||||
error = `File too large (${page.getPrettyBytes(file.size)}).`
|
error = `File too large (${page.getPrettyBytes(file.size)}).`
|
||||||
|
|
||||||
page.updateTemplateIcon(file.previewElement, 'icon-block')
|
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('.name').innerHTML = file.name
|
||||||
file.previewElement.querySelector('.error').innerHTML = error.description || error
|
file.previewElement.querySelector('.error').innerHTML = error.description || error
|
||||||
})
|
})
|
||||||
@ -362,7 +363,7 @@ page.uploadUrls = function (button) {
|
|||||||
// eslint-disable-next-line prefer-promise-reject-errors
|
// eslint-disable-next-line prefer-promise-reject-errors
|
||||||
return done('You have not entered any URLs.')
|
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 files = urls.map(function (url) {
|
||||||
const previewTemplate = document.createElement('template')
|
const previewTemplate = document.createElement('template')
|
||||||
previewTemplate.innerHTML = page.previewTemplate.trim()
|
previewTemplate.innerHTML = page.previewTemplate.trim()
|
||||||
@ -377,7 +378,7 @@ page.uploadUrls = function (button) {
|
|||||||
return done()
|
return done()
|
||||||
|
|
||||||
function posted (result) {
|
function posted (result) {
|
||||||
files[i].previewElement.querySelector('.progress').style.display = 'none'
|
files[i].previewElement.querySelector('.progress').classList.add('is-hidden')
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
page.updateTemplate(files[i], result.files[0])
|
page.updateTemplate(files[i], result.files[0])
|
||||||
} else {
|
} else {
|
||||||
@ -408,7 +409,7 @@ page.updateTemplateIcon = function (templateElement, iconClass) {
|
|||||||
const iconElement = templateElement.querySelector('.icon')
|
const iconElement = templateElement.querySelector('.icon')
|
||||||
if (!iconElement) return
|
if (!iconElement) return
|
||||||
iconElement.classList.add(iconClass)
|
iconElement.classList.add(iconClass)
|
||||||
iconElement.style.display = ''
|
iconElement.classList.remove('is-hidden')
|
||||||
}
|
}
|
||||||
|
|
||||||
page.updateTemplate = function (file, response) {
|
page.updateTemplate = function (file, response) {
|
||||||
@ -417,19 +418,19 @@ page.updateTemplate = function (file, response) {
|
|||||||
const a = file.previewElement.querySelector('.link > a')
|
const a = file.previewElement.querySelector('.link > a')
|
||||||
const clipboard = file.previewElement.querySelector('.clipboard-mobile > .clipboard-js')
|
const clipboard = file.previewElement.querySelector('.clipboard-mobile > .clipboard-js')
|
||||||
a.href = a.innerHTML = clipboard.dataset.clipboardText = response.url
|
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)
|
const exec = /.[\w]+(\?|$)/.exec(response.url)
|
||||||
if (exec && exec[0] && page.imageExtensions.includes(exec[0].toLowerCase())) {
|
if (exec && exec[0] && page.imageExtensions.includes(exec[0].toLowerCase())) {
|
||||||
const img = file.previewElement.querySelector('img')
|
const img = file.previewElement.querySelector('img')
|
||||||
img.setAttribute('alt', response.name || '')
|
img.setAttribute('alt', response.name || '')
|
||||||
img.dataset.src = response.url
|
img.dataset.src = response.url
|
||||||
img.style.display = ''
|
img.classList.remove('is-hidden')
|
||||||
img.onerror = function () {
|
img.onerror = function () {
|
||||||
// Hide image elements that fail to load
|
// Hide image elements that fail to load
|
||||||
// Consequently include WEBP in browsers that do not have WEBP support (Firefox/IE)
|
// Consequently include WEBP in browsers that do not have WEBP support (Firefox/IE)
|
||||||
this.style.display = 'none'
|
this.classList.add('is-hidden')
|
||||||
file.previewElement.querySelector('.icon').style.display = ''
|
file.previewElement.querySelector('.icon').classList.remove('is-hidden')
|
||||||
}
|
}
|
||||||
page.lazyLoad.update(file.previewElement.querySelectorAll('img'))
|
page.lazyLoad.update(file.previewElement.querySelectorAll('img'))
|
||||||
} else {
|
} else {
|
||||||
@ -439,7 +440,7 @@ page.updateTemplate = function (file, response) {
|
|||||||
if (response.expirydate) {
|
if (response.expirydate) {
|
||||||
const expiryDate = file.previewElement.querySelector('.expiry-date')
|
const expiryDate = file.previewElement.querySelector('.expiry-date')
|
||||||
expiryDate.innerHTML = `Expiry date: ${page.getPrettyDate(new Date(response.expirydate * 1000))}`
|
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
|
page.fileLength = stored
|
||||||
}
|
}
|
||||||
|
|
||||||
fileLengthDiv.style.display = 'block'
|
fileLengthDiv.classList.remove('is-hidden')
|
||||||
fileLengthDiv.querySelector('.help').innerHTML = helpText
|
fileLengthDiv.querySelector('.help').innerHTML = helpText
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -597,7 +598,7 @@ page.prepareUploadConfig = function () {
|
|||||||
page.uploadAge = stored
|
page.uploadAge = stored
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
uploadAgeDiv.style.display = 'block'
|
uploadAgeDiv.classList.remove('is-hidden')
|
||||||
}
|
}
|
||||||
|
|
||||||
const tabContent = document.querySelector('#tab-config')
|
const tabContent = document.querySelector('#tab-config')
|
||||||
@ -664,8 +665,9 @@ window.addEventListener('paste', function (event) {
|
|||||||
const item = items[index[i]]
|
const item = items[index[i]]
|
||||||
if (item.kind === 'file') {
|
if (item.kind === 'file') {
|
||||||
const blob = item.getAsFile()
|
const blob = item.getAsFile()
|
||||||
const file = new File([blob], `pasted-image.${blob.type.match(/(?:[^/]*\/)([^;]*)/)[1]}`)
|
const file = new File([blob], `pasted-image.${blob.type.match(/(?:[^/]*\/)([^;]*)/)[1]}`, {
|
||||||
file.type = blob.type
|
type: blob.type
|
||||||
|
})
|
||||||
page.dropzone.addFile(file)
|
page.dropzone.addFile(file)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
v3: CSS and JS files (libs such as bulma, lazyload, etc).
|
v3: CSS and JS files (libs such as bulma, lazyload, etc).
|
||||||
v4: Renders in /public/render/* directories (to be used by render.js).
|
v4: Renders in /public/render/* directories (to be used by render.js).
|
||||||
#}
|
#}
|
||||||
{% set v1 = "o59f6p3y1F" %}
|
{% set v1 = "1QupbESWeT" %}
|
||||||
{% set v2 = "hiboQUzAzp" %}
|
{% set v2 = "hiboQUzAzp" %}
|
||||||
{% set v3 = "tWLiAlAX5i" %}
|
{% set v3 = "tWLiAlAX5i" %}
|
||||||
{% set v4 = "S3TAWpPeFS" %}
|
{% set v4 = "S3TAWpPeFS" %}
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{{ super() }}
|
{{ super() }}
|
||||||
<section id="dashboard" class="section">
|
<section id="dashboard" class="section is-hidden">
|
||||||
<div id="panel" class="container">
|
<div id="panel" class="container">
|
||||||
<h1 class="title">
|
<h1 class="title">
|
||||||
Dashboard
|
Dashboard
|
||||||
@ -42,28 +42,28 @@
|
|||||||
<a id="itemUploads">Uploads</a>
|
<a id="itemUploads">Uploads</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a id="itemDeleteByNames">Delete by names</a>
|
<a id="itemDeleteUploadsByNames">Delete uploads by names</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p class="menu-label">Albums</p>
|
<p class="menu-label">Albums</p>
|
||||||
<ul class="menu-list">
|
<ul class="menu-list">
|
||||||
<li>
|
<li>
|
||||||
<a id="itemManageGallery">Manage your albums</a>
|
<a id="itemManageAlbums">Manage your albums</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<ul id="albumsContainer"></ul>
|
<ul id="albumsContainer"></ul>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p id="itemLabelAdmin" class="menu-label" style="display: none">Administration</p>
|
<p id="itemLabelAdmin" class="menu-label is-hidden">Administration</p>
|
||||||
<ul id="itemListAdmin" class="menu-list" style="display: none">
|
<ul id="itemListAdmin" class="menu-list is-hidden">
|
||||||
<li>
|
<li>
|
||||||
<a id="itemServerStats">Statistics</a>
|
<a id="itemStatistics" class="is-hidden">Statistics</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a id="itemManageUploads">Manage uploads</a>
|
<a id="itemManageUploads" class="is-hidden">Manage uploads</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a id="itemManageUsers">Manage users</a>
|
<a id="itemManageUsers" class="is-hidden">Manage users</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p class="menu-label">Configuration</p>
|
<p class="menu-label">Configuration</p>
|
||||||
@ -72,10 +72,10 @@
|
|||||||
<a id="ShareX">ShareX user profile</a>
|
<a id="ShareX">ShareX user profile</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a id="itemTokens">Manage your token</a>
|
<a id="itemManageToken">Manage your token</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a id="itemPassword">Change your password</a>
|
<a id="itemChangePassword">Change your password</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a id="itemLogout">Logout</a>
|
<a id="itemLogout">Logout</a>
|
||||||
|
@ -43,8 +43,8 @@
|
|||||||
<div class="columns is-gapless">
|
<div class="columns is-gapless">
|
||||||
<div class="column is-hidden-mobile"></div>
|
<div class="column is-hidden-mobile"></div>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<a id="loginToUpload" class="button is-danger is-loading" style="display: flex"></a>
|
<button id="loginToUpload" class="button is-danger is-fullwidth is-loading"></button>
|
||||||
<div id="albumDiv" class="field has-addons" style="display: none">
|
<div id="albumDiv" class="field has-addons is-hidden">
|
||||||
<div class="control is-expanded">
|
<div class="control is-expanded">
|
||||||
<div class="select is-fullwidth">
|
<div class="select is-fullwidth">
|
||||||
<select id="albumSelect"></select>
|
<select id="albumSelect"></select>
|
||||||
@ -56,7 +56,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
<ul>
|
||||||
<li data-id="tab-files" class="is-active">
|
<li data-id="tab-files" class="is-active">
|
||||||
<a>
|
<a>
|
||||||
@ -80,12 +80,12 @@
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</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 dz-container"></div>
|
||||||
<div class="field uploads"></div>
|
<div class="field uploads"></div>
|
||||||
</div>
|
</div>
|
||||||
{% if urlMaxSize -%}
|
{% 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="field">
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<textarea id="urls" class="textarea" rows="2"></textarea>
|
<textarea id="urls" class="textarea" rows="2"></textarea>
|
||||||
@ -119,7 +119,7 @@
|
|||||||
<div class="field uploads"></div>
|
<div class="field uploads"></div>
|
||||||
</div>
|
</div>
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
<div id="tab-config" class="tab-content" style="display: none">
|
<div id="tab-config" class="tab-content is-hidden">
|
||||||
<form>
|
<form>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label">File size display</label>
|
<label class="label">File size display</label>
|
||||||
@ -133,7 +133,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<p class="help">This will be used in our homepage, dashboard, and album public pages.</p>
|
<p class="help">This will be used in our homepage, dashboard, and album public pages.</p>
|
||||||
</div>
|
</div>
|
||||||
<div id="fileLengthDiv" class="field" style="display: none">
|
<div id="fileLengthDiv" class="field is-hidden">
|
||||||
<label class="label">File identifier length</label>
|
<label class="label">File identifier length</label>
|
||||||
<div class="control is-expanded">
|
<div class="control is-expanded">
|
||||||
<input id="fileLength" class="input is-fullwidth" type="number" min="0">
|
<input id="fileLength" class="input is-fullwidth" type="number" min="0">
|
||||||
@ -141,7 +141,7 @@
|
|||||||
<p class="help"></p>
|
<p class="help"></p>
|
||||||
</div>
|
</div>
|
||||||
{%- if temporaryUploadAges %}
|
{%- if temporaryUploadAges %}
|
||||||
<div id="uploadAgeDiv" class="field" style="display: none">
|
<div id="uploadAgeDiv" class="field is-hidden">
|
||||||
<label class="label">Upload age</label>
|
<label class="label">Upload age</label>
|
||||||
<div class="control is-expanded">
|
<div class="control is-expanded">
|
||||||
<div class="select is-fullwidth">
|
<div class="select is-fullwidth">
|
||||||
@ -185,18 +185,18 @@
|
|||||||
<div class="column is-hidden-mobile"></div>
|
<div class="column is-hidden-mobile"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="tpl" style="display: none">
|
<div id="tpl" class="is-hidden">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<i class="icon" style="display: none"></i>
|
<i class="icon is-hidden"></i>
|
||||||
<img class="is-unselectable" style="display: none">
|
<img class="is-unselectable is-hidden">
|
||||||
<p class="name is-unselectable"></p>
|
<p class="name is-unselectable"></p>
|
||||||
<progress class="progress is-small is-danger" max="100" value="0"></progress>
|
<progress class="progress is-small is-danger" max="100" value="0"></progress>
|
||||||
<p class="error"></p>
|
<p class="error"></p>
|
||||||
<p class="link">
|
<p class="link">
|
||||||
<a target="_blank" rel="noopener"></a>
|
<a target="_blank" rel="noopener"></a>
|
||||||
</p>
|
</p>
|
||||||
<p class="help expiry-date" style="display: none"></p>
|
<p class="help expiry-date is-hidden"></p>
|
||||||
<p class="clipboard-mobile" style="display: none">
|
<p class="clipboard-mobile is-hidden">
|
||||||
<a class="button is-small is-info is-outlined clipboard-js" style="display: flex">
|
<a class="button is-small is-info is-outlined clipboard-js" style="display: flex">
|
||||||
<span class="icon">
|
<span class="icon">
|
||||||
<i class="icon-clipboard-1"></i>
|
<i class="icon-clipboard-1"></i>
|
||||||
|
Loading…
Reference in New Issue
Block a user