mirror of
https://github.com/BobbyWibowo/lolisafe.git
synced 2025-01-19 01:31:34 +00:00
Massively overhauled uploads filtering endpoint
Please consult the Help? button again to learn all the syntax changes! The prompt will now also have its width expanded! Updated dependency, knex: 0.20.13 -> 0.20.15. Added new dependency: search-query-parser. Updated all sub-dependencies. Critical? Admins-only API /users/edit will no longer return NEW password salt of the user when randomizing their password. Added page.escape() function to js/misc/utils.js. This will be used to escape input in upload filters input box. The same function used in utilsController.js. Pretty dates will now use / instead of - for date separator. This is due to the fact that date range key for filtering uploads can not accepts dates with - separator. To avoid inconsistency, we will now use / separator. Caching system of album public pages will now be disabled during development (yarn develop). Cleaned up domClick() function in js/dashboard.js. If using date or expiry range keys when filtering uploads, attach client's timezone offset to the API requets. This will be used by the server to calculate timezone differences. Success prompt when changing token will now auto-close. Removed ID column from Manage Users. Improved success prompt when editing users. This will properly list all of the edited fields at once, excluding user group change. Success message for user group change will require a bit more changes on the API endpoint, which is a bit annoying. Rebuilt client-side assets and bumped v1 version string.
This commit is contained in:
parent
1c260c87b0
commit
3e3878b93c
@ -259,7 +259,7 @@ self.editUser = async (req, res, next) => {
|
|||||||
utils.invalidateStatsCache('users')
|
utils.invalidateStatsCache('users')
|
||||||
|
|
||||||
const response = { success: true, update }
|
const response = { success: true, update }
|
||||||
if (password) response.password = password
|
if (password) response.update.password = password
|
||||||
return res.json(response)
|
return res.json(response)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error)
|
logger.error(error)
|
||||||
|
@ -4,6 +4,7 @@ const fs = require('fs')
|
|||||||
const multer = require('multer')
|
const multer = require('multer')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const randomstring = require('randomstring')
|
const randomstring = require('randomstring')
|
||||||
|
const searchQuery = require('search-query-parser')
|
||||||
const paths = require('./pathsController')
|
const paths = require('./pathsController')
|
||||||
const perms = require('./permissionController')
|
const perms = require('./permissionController')
|
||||||
const utils = require('./utilsController')
|
const utils = require('./utilsController')
|
||||||
@ -758,94 +759,172 @@ self.list = async (req, res) => {
|
|||||||
|
|
||||||
const all = Boolean(req.headers.all)
|
const all = Boolean(req.headers.all)
|
||||||
const filters = req.headers.filters
|
const filters = req.headers.filters
|
||||||
|
const minoffset = req.headers.minoffset
|
||||||
const ismoderator = perms.is(user, 'moderator')
|
const ismoderator = perms.is(user, 'moderator')
|
||||||
if ((all || filters) && !ismoderator)
|
if ((all || filters) && !ismoderator)
|
||||||
return res.status(403).end()
|
return res.status(403).end()
|
||||||
|
|
||||||
const basedomain = config.domain
|
const basedomain = config.domain
|
||||||
|
|
||||||
// For filtering uploads
|
const filterObj = {
|
||||||
const _filters = {
|
|
||||||
uploaders: [],
|
uploaders: [],
|
||||||
names: [],
|
excludeUploaders: [],
|
||||||
ips: [],
|
queries: {
|
||||||
flags: {
|
exclude: {}
|
||||||
nouser: false,
|
|
||||||
noip: false
|
|
||||||
},
|
},
|
||||||
keywords: []
|
flags: {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cast column(s) to specific type if they're stored differently
|
const orderByObj = {
|
||||||
const _orderByCasts = {
|
// Cast columns to specific type if they are stored differently
|
||||||
|
casts: {
|
||||||
size: 'integer'
|
size: 'integer'
|
||||||
}
|
},
|
||||||
|
// Columns mapping
|
||||||
|
maps: {
|
||||||
|
date: 'timestamp',
|
||||||
|
expiry: 'expirydate'
|
||||||
|
},
|
||||||
// Columns with which to use SQLite's NULLS LAST option
|
// Columns with which to use SQLite's NULLS LAST option
|
||||||
const _orderByNullsLast = [
|
nullsLast: [
|
||||||
'userid',
|
'userid',
|
||||||
'expirydate',
|
'expirydate',
|
||||||
'ip'
|
'ip'
|
||||||
]
|
],
|
||||||
const _orderBy = []
|
parsed: []
|
||||||
|
}
|
||||||
|
|
||||||
// Perhaps this can be simplified even further?
|
|
||||||
if (filters) {
|
if (filters) {
|
||||||
const usernames = []
|
const keywords = [
|
||||||
filters
|
'ip',
|
||||||
.split(' ')
|
'user'
|
||||||
.map((v, i, a) => {
|
]
|
||||||
if (/[^\\]\\$/.test(v) && a[i + 1]) {
|
const ranges = [
|
||||||
const tmp = `${v.slice(0, -1)} ${a[i + 1]}`
|
'date',
|
||||||
a[i + 1] = ''
|
'expiry'
|
||||||
return tmp
|
]
|
||||||
}
|
filterObj.queries = searchQuery.parse(filters, {
|
||||||
return v.replace(/\\\\/, '\\')
|
keywords: keywords.concat([
|
||||||
|
'orderby'
|
||||||
|
]),
|
||||||
|
ranges,
|
||||||
|
tokenize: true,
|
||||||
|
alwaysArray: true,
|
||||||
|
offsets: false
|
||||||
})
|
})
|
||||||
.map((v, i) => {
|
|
||||||
const x = v.indexOf(':')
|
for (const key of keywords)
|
||||||
if (x >= 0 && v.substring(x + 1))
|
if (filterObj.queries[key]) {
|
||||||
return [v.substring(0, x), v.substring(x + 1)]
|
// Make sure keyword arrays only contain unique values
|
||||||
else
|
filterObj.queries[key] = filterObj.queries[key].filter((v, i, a) => a.indexOf(v) === i)
|
||||||
return v
|
|
||||||
})
|
// Flag to match NULL values
|
||||||
.forEach(v => {
|
const index = filterObj.queries[key].indexOf('-')
|
||||||
if (Array.isArray(v)) {
|
if (index !== -1) {
|
||||||
if (v[0] === 'user') {
|
filterObj.flags[`no${key}`] = true
|
||||||
usernames.push(v[1])
|
filterObj.queries[key].splice(index, 1)
|
||||||
} else if (v[0] === 'name') {
|
|
||||||
_filters.names.push(v[1])
|
|
||||||
} else if (v[0] === 'ip') {
|
|
||||||
_filters.ips.push(v[1])
|
|
||||||
} else if (v[0] === 'orderby') {
|
|
||||||
const tmp = v[1].split(':')
|
|
||||||
let col = tmp[0]
|
|
||||||
let dir = 'asc'
|
|
||||||
if (_orderByCasts[col])
|
|
||||||
col = `cast (\`${col}\` as ${_orderByCasts[col]})`
|
|
||||||
if (tmp[1] && /^d/i.test(tmp[1]))
|
|
||||||
dir = 'desc'
|
|
||||||
_orderBy.push(`${col} ${dir}${_orderByNullsLast.includes(col) ? ' nulls last' : ''}`)
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const parseDate = (date, minoffset, resetMs) => {
|
||||||
|
// [YYYY][/MM][/DD] [HH][:MM][:SS]
|
||||||
|
// e.g. 2020/01/01 00:00:00, 2018/01/01 06, 2019/11, 12:34:00
|
||||||
|
const match = date.match(/^(\d{4})?(\/\d{2})?(\/\d{2})?\s?(\d{2})?(:\d{2})?(:\d{2})?$/)
|
||||||
|
|
||||||
|
if (match) {
|
||||||
|
const offset = 60000 * (utils.timezoneOffset - minoffset)
|
||||||
|
const dateObj = new Date(Date.now() + offset)
|
||||||
|
|
||||||
|
if (match[1] !== undefined)
|
||||||
|
dateObj.setFullYear(Number(match[1]), // full year
|
||||||
|
match[2] !== undefined ? (Number(match[2].slice(1)) - 1) : 0, // month, zero-based
|
||||||
|
match[3] !== undefined ? Number(match[3].slice(1)) : 1) // date
|
||||||
|
|
||||||
|
if (match[4] !== undefined)
|
||||||
|
dateObj.setHours(Number(match[4]), // hours
|
||||||
|
match[5] !== undefined ? Number(match[5].slice(1)) : 0, // minutes
|
||||||
|
match[6] !== undefined ? Number(match[6].slice(1)) : 0) // seconds
|
||||||
|
|
||||||
|
if (resetMs)
|
||||||
|
dateObj.setMilliseconds(0)
|
||||||
|
|
||||||
|
// Calculate timezone differences
|
||||||
|
const newDateObj = new Date(dateObj.getTime() - offset)
|
||||||
|
return newDateObj
|
||||||
} else {
|
} else {
|
||||||
if (v === '-user')
|
return null
|
||||||
_filters.flags.nouser = true
|
|
||||||
else if (v === '-ip')
|
|
||||||
_filters.flags.noip = true
|
|
||||||
else
|
|
||||||
_filters.keywords.push(v[0])
|
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
_filters.uploaders = await db.table('users')
|
|
||||||
|
// Parse dates to timestamps
|
||||||
|
for (const range of ranges)
|
||||||
|
if (filterObj.queries[range]) {
|
||||||
|
if (filterObj.queries[range].from) {
|
||||||
|
const parsed = parseDate(filterObj.queries[range].from, minoffset, true)
|
||||||
|
filterObj.queries[range].from = parsed ? Math.floor(parsed / 1000) : null
|
||||||
|
}
|
||||||
|
if (filterObj.queries[range].to) {
|
||||||
|
const parsed = parseDate(filterObj.queries[range].to, minoffset, true)
|
||||||
|
filterObj.queries[range].to = parsed ? Math.ceil(parsed / 1000) : null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query users table for user IDs
|
||||||
|
if (filterObj.queries.user || filterObj.queries.exclude.user) {
|
||||||
|
const usernames = []
|
||||||
|
.concat(filterObj.queries.user || [])
|
||||||
|
.concat(filterObj.queries.exclude.user || [])
|
||||||
|
|
||||||
|
const uploaders = await db.table('users')
|
||||||
.whereIn('username', usernames)
|
.whereIn('username', usernames)
|
||||||
.select('id', 'username')
|
.select('id', 'username')
|
||||||
|
|
||||||
|
// If no matches, or mismatched results
|
||||||
|
if (!uploaders || (uploaders.length !== usernames.length)) {
|
||||||
|
const notFound = usernames.filter(username => {
|
||||||
|
return !uploaders.find(uploader => uploader.username === username)
|
||||||
|
})
|
||||||
|
if (notFound)
|
||||||
|
return res.json({
|
||||||
|
success: false,
|
||||||
|
description: `User${notFound.length === 1 ? '' : 's'} not found: ${notFound.join(', ')}.`
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filters && !(_filters.uploaders.length || _filters.names.length || _filters.ips.length || _filters.flags.nouser || _filters.flags.noip || _orderBy.length))
|
for (const uploader of uploaders)
|
||||||
if (_filters.keywords.length)
|
if (filterObj.queries.user && filterObj.queries.user.includes(uploader.username))
|
||||||
// TODO: Support filtering using keywords only
|
filterObj.uploaders.push(uploader)
|
||||||
return res.json({ success: false, description: 'Filtering using keywords only is still work in progress. Please confirm valid filtering keys through the Help? button!' })
|
|
||||||
else
|
else
|
||||||
return res.json({ success: false, description: 'No valid filter or sort keys were used. Please confirm the valid keys through the Help? button!' })
|
filterObj.excludeUploaders.push(uploader)
|
||||||
|
|
||||||
|
delete filterObj.queries.user
|
||||||
|
delete filterObj.queries.exclude.user
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse orderby keys
|
||||||
|
if (filterObj.queries.orderby) {
|
||||||
|
for (const obQuery of filterObj.queries.orderby) {
|
||||||
|
const tmp = obQuery.toLowerCase().split(':')
|
||||||
|
|
||||||
|
let column = orderByObj.maps[tmp[0]] || tmp[0]
|
||||||
|
let direction = 'asc'
|
||||||
|
|
||||||
|
if (orderByObj.casts[column])
|
||||||
|
column = `cast (\`${column}\` as ${orderByObj.casts[column]})`
|
||||||
|
if (tmp[1] && /^d/.test(tmp[1]))
|
||||||
|
direction = 'desc'
|
||||||
|
|
||||||
|
const suffix = orderByObj.nullsLast.includes(column) ? ' nulls last' : ''
|
||||||
|
orderByObj.parsed.push(`${column} ${direction}${suffix}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
delete filterObj.queries.orderby
|
||||||
|
}
|
||||||
|
|
||||||
|
// For some reason, single value won't be in Array even with 'alwaysArray' option
|
||||||
|
if (typeof filterObj.queries.exclude.text === 'string')
|
||||||
|
filterObj.queries.exclude.text = [filterObj.queries.exclude.text]
|
||||||
|
}
|
||||||
|
|
||||||
function filter () {
|
function filter () {
|
||||||
if (req.params.id !== undefined)
|
if (req.params.id !== undefined)
|
||||||
@ -853,23 +932,53 @@ self.list = async (req, res) => {
|
|||||||
else if (!all)
|
else if (!all)
|
||||||
this.where('userid', user.id)
|
this.where('userid', user.id)
|
||||||
else
|
else
|
||||||
// Fisrt, look for uploads matching ANY of the supplied 'user' OR 'ip' filters
|
// Sheesh, these look too overbearing...
|
||||||
// Then, refine the matches using the supplied 'name' filters
|
|
||||||
this.where(function () {
|
this.where(function () {
|
||||||
if (_filters.uploaders.length)
|
// Filter uploads matching any of the supplied 'user' keys and/or NULL flag
|
||||||
this.orWhereIn('userid', _filters.uploaders.map(v => v.id))
|
if (filterObj.uploaders.length)
|
||||||
if (_filters.ips.length)
|
this.orWhereIn('userid', filterObj.uploaders.map(v => v.id))
|
||||||
this.orWhereIn('ip', _filters.ips)
|
if (filterObj.excludeUploaders.length)
|
||||||
if (_filters.flags.nouser)
|
this.orWhereNotIn('userid', filterObj.excludeUploaders.map(v => v.id))
|
||||||
|
if (filterObj.flags.nouser)
|
||||||
this.orWhereNull('userid')
|
this.orWhereNull('userid')
|
||||||
if (_filters.flags.noip)
|
}).orWhere(function () {
|
||||||
|
// Filter uploads matching any of the supplied 'ip' keys and/or NULL flag
|
||||||
|
if (filterObj.queries.ip)
|
||||||
|
this.orWhereIn('ip', filterObj.queries.ip)
|
||||||
|
if (filterObj.queries.exclude.ip)
|
||||||
|
this.orWhereNotIn('ip', filterObj.queries.exclude.ip)
|
||||||
|
if (filterObj.flags.noip)
|
||||||
this.orWhereNull('ip')
|
this.orWhereNull('ip')
|
||||||
}).andWhere(function () {
|
}).andWhere(function () {
|
||||||
for (const name of _filters.names)
|
// Then, refine using the supplied 'date' and/or 'expiry' ranges
|
||||||
|
if (filterObj.queries.date)
|
||||||
|
if (typeof filterObj.queries.date.to === 'number')
|
||||||
|
this.andWhereBetween('timestamp', [filterObj.queries.date.from, filterObj.queries.date.to])
|
||||||
|
else
|
||||||
|
this.andWhere('timestamp', '>=', filterObj.queries.date.from)
|
||||||
|
if (filterObj.queries.expiry)
|
||||||
|
if (typeof filterObj.queries.expiry.to === 'number')
|
||||||
|
this.andWhereBetween('expirydate', [filterObj.queries.expiry.from, filterObj.queries.expiry.to])
|
||||||
|
else
|
||||||
|
this.andWhere('expirydate', '>=', filterObj.queries.date.from)
|
||||||
|
}).andWhere(function () {
|
||||||
|
// Then, refine using the supplied keywords against their file names
|
||||||
|
if (!filterObj.queries.text) return
|
||||||
|
for (const name of filterObj.queries.text)
|
||||||
if (name.includes('*'))
|
if (name.includes('*'))
|
||||||
this.orWhere('name', 'like', name.replace(/\*/g, '%'))
|
this.orWhere('name', 'like', name.replace(/\*/g, '%'))
|
||||||
else
|
else
|
||||||
this.orWhere('name', name)
|
// If no asterisks, assume partial
|
||||||
|
this.orWhere('name', 'like', `%${name}%`)
|
||||||
|
}).andWhere(function () {
|
||||||
|
// Finally, refine using the supplied exclusions against their file names
|
||||||
|
if (!filterObj.queries.exclude.text) return
|
||||||
|
for (const exclude of filterObj.queries.exclude.text)
|
||||||
|
if (exclude.includes('*'))
|
||||||
|
this.orWhere('name', 'not like', exclude.replace(/\*/g, '%'))
|
||||||
|
else
|
||||||
|
// If no asterisks, assume partial
|
||||||
|
this.orWhere('name', 'not like', `%${exclude}%`)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -886,16 +995,18 @@ self.list = async (req, res) => {
|
|||||||
if (offset === undefined) offset = 0
|
if (offset === undefined) offset = 0
|
||||||
|
|
||||||
const columns = ['id', 'name', 'userid', 'size', 'timestamp']
|
const columns = ['id', 'name', 'userid', 'size', 'timestamp']
|
||||||
|
|
||||||
if (temporaryUploads)
|
if (temporaryUploads)
|
||||||
columns.push('expirydate')
|
columns.push('expirydate')
|
||||||
|
|
||||||
// Only select IPs if we are listing all uploads
|
// Only select IPs if we are listing all uploads
|
||||||
columns.push(all ? 'ip' : 'albumid')
|
columns.push(all ? 'ip' : 'albumid')
|
||||||
|
|
||||||
|
const orderByRaw = orderByObj.parsed.length
|
||||||
|
? orderByObj.parsed.join(', ')
|
||||||
|
: '`id` desc'
|
||||||
const files = await db.table('files')
|
const files = await db.table('files')
|
||||||
.where(filter)
|
.where(filter)
|
||||||
.orderByRaw(_orderBy.length ? _orderBy.join(', ') : '`id` desc')
|
.orderByRaw(orderByRaw)
|
||||||
.limit(25)
|
.limit(25)
|
||||||
.offset(25 * offset)
|
.offset(25 * offset)
|
||||||
.select(columns)
|
.select(columns)
|
||||||
@ -936,8 +1047,8 @@ self.list = async (req, res) => {
|
|||||||
return res.json({ success: true, files, count, albums, basedomain })
|
return res.json({ success: true, files, count, albums, basedomain })
|
||||||
|
|
||||||
// Otherwise proceed to querying usernames
|
// Otherwise proceed to querying usernames
|
||||||
let _users = _filters.uploaders
|
let usersTable = filterObj.uploaders
|
||||||
if (!_users.length) {
|
if (!usersTable.length) {
|
||||||
const userids = files
|
const userids = files
|
||||||
.map(file => file.userid)
|
.map(file => file.userid)
|
||||||
.filter((v, i, a) => {
|
.filter((v, i, a) => {
|
||||||
@ -949,13 +1060,13 @@ self.list = async (req, res) => {
|
|||||||
return res.json({ success: true, files, count, basedomain })
|
return res.json({ success: true, files, count, basedomain })
|
||||||
|
|
||||||
// Query usernames of user IDs from currently selected files
|
// Query usernames of user IDs from currently selected files
|
||||||
_users = await db.table('users')
|
usersTable = await db.table('users')
|
||||||
.whereIn('id', userids)
|
.whereIn('id', userids)
|
||||||
.select('id', 'username')
|
.select('id', 'username')
|
||||||
}
|
}
|
||||||
|
|
||||||
const users = {}
|
const users = {}
|
||||||
for (const user of _users)
|
for (const user of usersTable)
|
||||||
users[user.id] = user.username
|
users[user.id] = user.username
|
||||||
|
|
||||||
return res.json({ success: true, files, count, users, basedomain })
|
return res.json({ success: true, files, count, users, basedomain })
|
||||||
|
@ -31,7 +31,8 @@ const self = {
|
|||||||
|
|
||||||
ffprobe: promisify(ffmpeg.ffprobe),
|
ffprobe: promisify(ffmpeg.ffprobe),
|
||||||
|
|
||||||
albumsCache: {}
|
albumsCache: {},
|
||||||
|
timezoneOffset: new Date().getTimezoneOffset()
|
||||||
}
|
}
|
||||||
|
|
||||||
const statsCache = {
|
const statsCache = {
|
||||||
@ -106,7 +107,7 @@ self.extname = filename => {
|
|||||||
return extname + multi
|
return extname + multi
|
||||||
}
|
}
|
||||||
|
|
||||||
self.escape = (string) => {
|
self.escape = string => {
|
||||||
// MIT License
|
// MIT License
|
||||||
// Copyright(c) 2012-2013 TJ Holowaychuk
|
// Copyright(c) 2012-2013 TJ Holowaychuk
|
||||||
// Copyright(c) 2015 Andreas Lubbe
|
// Copyright(c) 2015 Andreas Lubbe
|
||||||
|
2
dist/js/dashboard.js
vendored
2
dist/js/dashboard.js
vendored
File diff suppressed because one or more lines are too long
2
dist/js/dashboard.js.map
vendored
2
dist/js/dashboard.js.map
vendored
File diff suppressed because one or more lines are too long
2
dist/js/misc/utils.js
vendored
2
dist/js/misc/utils.js
vendored
@ -1,2 +1,2 @@
|
|||||||
lsKeys.siBytes="siBytes",page.prepareShareX=function(){var e=page.token?{token:page.token||"",albumid:page.album||""}:{};e.filelength=page.fileLength||"",e.age=page.uploadAge||"",e.striptags=page.stripTags||"";for(var t=[],a=Object.keys(e),n=0;n<a.length;n++)t.push(' "'+a[n]+'": "'+e[a[n]]+'"');var o=(location.hostname+location.pathname).replace(/\/(dashboard)?$/,""),r=o.replace(/\//g,"_"),i=document.querySelector("#ShareX"),s='{\n "Name": "'+r+'",\n "DestinationType": "ImageUploader, FileUploader",\n "RequestMethod": "POST",\n "RequestURL": "'+location.protocol+"//"+o+'/api/upload",\n "Headers": {\n'+t.join(",\n")+'\n },\n "Body": "MultipartFormData",\n "FileFormName": "files[]",\n "URL": "$json:files[0].url$",\n "ThumbnailURL": "$json:files[0].url$"\n}',l=new Blob([s],{type:"application/octet-binary"});i.setAttribute("href",URL.createObjectURL(l)),i.setAttribute("download",r+".sxcu")},page.getPrettyDate=function(e){return e.getFullYear()+"-"+(e.getMonth()<9?"0":"")+(e.getMonth()+1)+"-"+(e.getDate()<10?"0":"")+e.getDate()+" "+(e.getHours()<10?"0":"")+e.getHours()+":"+(e.getMinutes()<10?"0":"")+e.getMinutes()+":"+(e.getSeconds()<10?"0":"")+e.getSeconds()},page.getPrettyBytes=function(e){if("number"!=typeof e&&!isFinite(e))return e;var t="0"!==localStorage[lsKeys.siBytes],a=e<0?"-":"",n=t?1e3:1024;if(a&&(e=-e),e<n)return""+a+e+" B";var o=Math.min(Math.floor(Math.log(e)*Math.LOG10E/3),8);return""+a+Number((e/Math.pow(n,o)).toPrecision(3))+" "+((t?"kMGTPEZY":"KMGTPEZY").charAt(o-1)+(t?"":"i"))+"B"};
|
lsKeys.siBytes="siBytes",page.prepareShareX=function(){var e=page.token?{token:page.token||"",albumid:page.album||""}:{};e.filelength=page.fileLength||"",e.age=page.uploadAge||"",e.striptags=page.stripTags||"";for(var t=[],a=Object.keys(e),r=0;r<a.length;r++)t.push(' "'+a[r]+'": "'+e[a[r]]+'"');var n=(location.hostname+location.pathname).replace(/\/(dashboard)?$/,""),o=n.replace(/\//g,"_"),i=document.querySelector("#ShareX"),s='{\n "Name": "'+o+'",\n "DestinationType": "ImageUploader, FileUploader",\n "RequestMethod": "POST",\n "RequestURL": "'+location.protocol+"//"+n+'/api/upload",\n "Headers": {\n'+t.join(",\n")+'\n },\n "Body": "MultipartFormData",\n "FileFormName": "files[]",\n "URL": "$json:files[0].url$",\n "ThumbnailURL": "$json:files[0].url$"\n}',l=new Blob([s],{type:"application/octet-binary"});i.setAttribute("href",URL.createObjectURL(l)),i.setAttribute("download",o+".sxcu")},page.getPrettyDate=function(e){return e.getFullYear()+"/"+(e.getMonth()<9?"0":"")+(e.getMonth()+1)+"/"+(e.getDate()<10?"0":"")+e.getDate()+" "+(e.getHours()<10?"0":"")+e.getHours()+":"+(e.getMinutes()<10?"0":"")+e.getMinutes()+":"+(e.getSeconds()<10?"0":"")+e.getSeconds()},page.getPrettyBytes=function(e){if("number"!=typeof e&&!isFinite(e))return e;var t="0"!==localStorage[lsKeys.siBytes],a=e<0?"-":"",r=t?1e3:1024;if(a&&(e=-e),e<r)return""+a+e+" B";var n=Math.min(Math.floor(Math.log(e)*Math.LOG10E/3),8);return""+a+Number((e/Math.pow(r,n)).toPrecision(3))+" "+((t?"kMGTPEZY":"KMGTPEZY").charAt(n-1)+(t?"":"i"))+"B"},page.escape=function(e){if(!e)return e;var t,a=String(e),r=/["'&<>]/.exec(a);if(!r)return a;var n="",o=0,i=0;for(o=r.index;o<a.length;o++){switch(a.charCodeAt(o)){case 34:t=""";break;case 38:t="&";break;case 39:t="'";break;case 60:t="<";break;case 62:t=">";break;default:continue}i!==o&&(n+=a.substring(i,o)),i=o+1,n+=t}return i!==o?n+a.substring(i,o):n};
|
||||||
//# sourceMappingURL=utils.js.map
|
//# sourceMappingURL=utils.js.map
|
||||||
|
2
dist/js/misc/utils.js.map
vendored
2
dist/js/misc/utils.js.map
vendored
File diff suppressed because one or more lines are too long
@ -37,12 +37,13 @@
|
|||||||
"fluent-ffmpeg": "^2.1.2",
|
"fluent-ffmpeg": "^2.1.2",
|
||||||
"helmet": "^3.22.0",
|
"helmet": "^3.22.0",
|
||||||
"jszip": "^3.3.0",
|
"jszip": "^3.3.0",
|
||||||
"knex": "^0.20.13",
|
"knex": "^0.20.15",
|
||||||
"multer": "^1.4.2",
|
"multer": "^1.4.2",
|
||||||
"node-fetch": "^2.6.0",
|
"node-fetch": "^2.6.0",
|
||||||
"nunjucks": "^3.2.1",
|
"nunjucks": "^3.2.1",
|
||||||
"randomstring": "^1.1.5",
|
"randomstring": "^1.1.5",
|
||||||
"readline": "^1.3.0",
|
"readline": "^1.3.0",
|
||||||
|
"search-query-parser": "^1.5.5",
|
||||||
"sharp": "^0.25.2",
|
"sharp": "^0.25.2",
|
||||||
"sqlite3": "^4.1.1",
|
"sqlite3": "^4.1.1",
|
||||||
"systeminformation": "^4.23.3"
|
"systeminformation": "^4.23.3"
|
||||||
|
@ -31,8 +31,10 @@ routes.get('/a/:identifier', async (req, res, next) => {
|
|||||||
|
|
||||||
const nojs = req.query.nojs !== undefined
|
const nojs = req.query.nojs !== undefined
|
||||||
|
|
||||||
|
let cacheid
|
||||||
|
if (process.env.NODE_ENV !== 'development') {
|
||||||
// Cache ID - we initialize a separate cache for No-JS version
|
// Cache ID - we initialize a separate cache for No-JS version
|
||||||
const cacheid = nojs ? `${album.id}-nojs` : album.id
|
cacheid = nojs ? `${album.id}-nojs` : album.id
|
||||||
|
|
||||||
if (!utils.albumsCache[cacheid])
|
if (!utils.albumsCache[cacheid])
|
||||||
utils.albumsCache[cacheid] = {
|
utils.albumsCache[cacheid] = {
|
||||||
@ -55,6 +57,7 @@ routes.get('/a/:identifier', async (req, res, next) => {
|
|||||||
// when an album is edited during this generation process.
|
// when an album is edited during this generation process.
|
||||||
utils.albumsCache[cacheid].generating = true
|
utils.albumsCache[cacheid].generating = true
|
||||||
utils.albumsCache[cacheid].generatedAt = Math.floor(Date.now() / 1000)
|
utils.albumsCache[cacheid].generatedAt = Math.floor(Date.now() / 1000)
|
||||||
|
}
|
||||||
|
|
||||||
const files = await db.table('files')
|
const files = await db.table('files')
|
||||||
.select('name', 'size')
|
.select('name', 'size')
|
||||||
@ -89,12 +92,15 @@ routes.get('/a/:identifier', async (req, res, next) => {
|
|||||||
files,
|
files,
|
||||||
nojs
|
nojs
|
||||||
}, (error, html) => {
|
}, (error, html) => {
|
||||||
utils.albumsCache[cacheid].cache = error ? null : html
|
const data = error ? null : html
|
||||||
|
if (cacheid) {
|
||||||
|
utils.albumsCache[cacheid].cache = data
|
||||||
utils.albumsCache[cacheid].generating = false
|
utils.albumsCache[cacheid].generating = false
|
||||||
|
}
|
||||||
|
|
||||||
// Express should already send error to the next handler
|
// Express should already send error to the next handler
|
||||||
if (error) return
|
if (error) return
|
||||||
return res.send(utils.albumsCache[cacheid].cache)
|
return res.send(data)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -298,34 +298,34 @@ page.domClick = event => {
|
|||||||
const action = element.dataset.action
|
const action = element.dataset.action
|
||||||
|
|
||||||
switch (action) {
|
switch (action) {
|
||||||
|
// Uploads
|
||||||
case 'view-list':
|
case 'view-list':
|
||||||
return page.setUploadsView('list', element)
|
return page.setUploadsView('list', element)
|
||||||
case 'view-thumbs':
|
case 'view-thumbs':
|
||||||
return page.setUploadsView('thumbs', element)
|
return page.setUploadsView('thumbs', element)
|
||||||
case 'clear-selection':
|
|
||||||
return page.clearSelection()
|
|
||||||
case 'add-selected-uploads-to-album':
|
|
||||||
return page.addSelectedUploadsToAlbum()
|
|
||||||
case 'select':
|
|
||||||
return page.select(element, event)
|
|
||||||
case 'select-all':
|
|
||||||
return page.selectAll(element)
|
|
||||||
case 'add-to-album':
|
case 'add-to-album':
|
||||||
return page.addToAlbum(id)
|
return page.addToAlbum(id)
|
||||||
case 'delete-upload':
|
case 'delete-upload':
|
||||||
return page.deleteUpload(id)
|
return page.deleteUpload(id)
|
||||||
|
case 'add-selected-uploads-to-album':
|
||||||
|
return page.addSelectedUploadsToAlbum()
|
||||||
case 'bulk-delete-uploads':
|
case 'bulk-delete-uploads':
|
||||||
return page.bulkDeleteUploads()
|
return page.bulkDeleteUploads()
|
||||||
case 'display-preview':
|
case 'display-preview':
|
||||||
return page.displayPreview(id)
|
return page.displayPreview(id)
|
||||||
|
// Manage uploads
|
||||||
|
case 'upload-filters-help':
|
||||||
|
return page.uploadFiltersHelp(element)
|
||||||
|
case 'filter-uploads':
|
||||||
|
return page.filterUploads(element)
|
||||||
|
// Manage your albums
|
||||||
case 'submit-album':
|
case 'submit-album':
|
||||||
return page.submitAlbum(element)
|
return page.submitAlbum(element)
|
||||||
case 'edit-album':
|
case 'edit-album':
|
||||||
return page.editAlbum(id)
|
return page.editAlbum(id)
|
||||||
case 'delete-album':
|
case 'delete-album':
|
||||||
return page.deleteAlbum(id)
|
return page.deleteAlbum(id)
|
||||||
case 'get-new-token':
|
// Manage users
|
||||||
return page.getNewToken(element)
|
|
||||||
case 'create-user':
|
case 'create-user':
|
||||||
return page.createUser()
|
return page.createUser()
|
||||||
case 'edit-user':
|
case 'edit-user':
|
||||||
@ -334,12 +334,24 @@ page.domClick = event => {
|
|||||||
return page.disableUser(id)
|
return page.disableUser(id)
|
||||||
case 'delete-user':
|
case 'delete-user':
|
||||||
return page.deleteUser(id)
|
return page.deleteUser(id)
|
||||||
case 'user-filters-help':
|
|
||||||
return page.userFiltersHelp(element)
|
|
||||||
case 'filter-uploads':
|
|
||||||
return page.filterUploads(element)
|
|
||||||
case 'view-user-uploads':
|
case 'view-user-uploads':
|
||||||
return page.viewUserUploads(id, element)
|
return page.viewUserUploads(id, element)
|
||||||
|
/* // WIP
|
||||||
|
case 'user-filters-help':
|
||||||
|
return page.userFiltersHelp(element)
|
||||||
|
case 'filter-users':
|
||||||
|
return page.filterUsers(element)
|
||||||
|
*/
|
||||||
|
// Others
|
||||||
|
case 'get-new-token':
|
||||||
|
return page.getNewToken(element)
|
||||||
|
// Uploads & Users
|
||||||
|
case 'clear-selection':
|
||||||
|
return page.clearSelection()
|
||||||
|
case 'select':
|
||||||
|
return page.select(element, event)
|
||||||
|
case 'select-all':
|
||||||
|
return page.selectAll(element)
|
||||||
case 'page-ellipsis':
|
case 'page-ellipsis':
|
||||||
return page.focusJumpToPage()
|
return page.focusJumpToPage()
|
||||||
case 'page-prev':
|
case 'page-prev':
|
||||||
@ -429,6 +441,11 @@ page.getUploads = (params = {}) => {
|
|||||||
filters: params.filters || ''
|
filters: params.filters || ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Send client timezone offset if using date filter
|
||||||
|
// Server will pretend client is on UTC if missing
|
||||||
|
if (headers.filters.includes('date:') || headers.filters.includes('expiry:'))
|
||||||
|
headers.minOffset = new Date().getTimezoneOffset()
|
||||||
|
|
||||||
axios.get(url, { headers }).then(response => {
|
axios.get(url, { headers }).then(response => {
|
||||||
if (response.data.success === false)
|
if (response.data.success === false)
|
||||||
if (response.data.description === 'No token provided') {
|
if (response.data.description === 'No token provided') {
|
||||||
@ -464,10 +481,10 @@ page.getUploads = (params = {}) => {
|
|||||||
<form class="prevent-default">
|
<form class="prevent-default">
|
||||||
<div class="field has-addons">
|
<div class="field has-addons">
|
||||||
<div class="control is-expanded">
|
<div class="control is-expanded">
|
||||||
<input id="filters" class="input is-small" type="text" placeholder="Filters" value="${params.filters || ''}">
|
<input id="filters" class="input is-small" type="text" placeholder="Filters" value="${page.escape(params.filters || '')}">
|
||||||
</div>
|
</div>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<button type="button" class="button is-small is-primary is-outlined" title="Help?" data-action="user-filters-help">
|
<button type="button" class="button is-small is-primary is-outlined" title="Help?" data-action="upload-filters-help">
|
||||||
<span class="icon">
|
<span class="icon">
|
||||||
<i class="icon-help-circled"></i>
|
<i class="icon-help-circled"></i>
|
||||||
</span>
|
</span>
|
||||||
@ -666,7 +683,7 @@ page.getUploads = (params = {}) => {
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th><input id="selectAll" class="checkbox" type="checkbox" title="Select all" data-action="select-all"></th>
|
<th><input id="selectAll" class="checkbox" type="checkbox" title="Select all" data-action="select-all"></th>
|
||||||
<th>File</th>
|
<th>File name</th>
|
||||||
${params.album === undefined ? `<th>${params.all ? 'User' : 'Album'}</th>` : ''}
|
${params.album === undefined ? `<th>${params.all ? 'User' : 'Album'}</th>` : ''}
|
||||||
<th>Size</th>
|
<th>Size</th>
|
||||||
${params.all ? '<th>IP</th>' : ''}
|
${params.all ? '<th>IP</th>' : ''}
|
||||||
@ -946,40 +963,68 @@ page.clearSelection = () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
page.userFiltersHelp = element => {
|
page.uploadFiltersHelp = 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> (upload name).
|
There are 2 filter keys, namely <b>user</b> (username) and <b>ip</b>.
|
||||||
Each key can be specified more than once.
|
These keys can be specified more than once.
|
||||||
Backslashes should be used if the username contain whitespaces.
|
For usernames with whitespaces, wrap them with double quotes (<b>"</b>).
|
||||||
|
|
||||||
There are 2 special flags, namely <b>-user</b> and <b>-ip</b>, which will match uploads by non-registered users and have no IPs respectively.
|
There are 2 special filter keys, namely <b>user:-</b> and <b>ip:-</b>, to match uploads by non-registered users and have no IPs respectively.
|
||||||
|
|
||||||
Matches can also be sorted with <b>orderby:columnName[:direction]</b> key.
|
To exclude certain users/ips while still listing every other uploads, add negation sign (<b>-</b>) before the keys.
|
||||||
This key requires using the internal column names used in the database (size, timestamp, expirydate, and so on).
|
<b>Unfortunately</b>, this will not show uploads by non-registered users or have no IPs as well, instead you need to also use the special filter keys if you want to include them.
|
||||||
|
|
||||||
|
There are 2 range keys: <b>date</b> (upload date) and <b>expiry</b> (expiry date).
|
||||||
|
Their format is: <b>YYYY/MM/DD HH:MM:SS-YYYY/MM/DD HH:MM:SS</b> ("to" date is optional).
|
||||||
|
If any of the subsequent date or time units are not specified, their first value will be used (e.g. January for month, 1 for day, and so on).
|
||||||
|
If only time is specified, today's date will be used.
|
||||||
|
Meaning the following can be accepted: <b>2020/01/01 01:23</b>, <b>2018/01/01 06</b>, <b>2019/11</b>, <b>12:34:56</b>.
|
||||||
|
These keys can only be specified once each.
|
||||||
|
|
||||||
|
Matches can also be sorted with <b>orderby:columnName[:d[escending]]</b> keys.
|
||||||
|
This key require internal column names used in the database (id, userid, and so on), but there are 2 shortcuts, namely <b>date</b> for timestamp column and <b>expiry</b> for expirydate column.
|
||||||
This key can also be specified more than once, where their order will decide the sorting steps.
|
This key can also be specified more than once, where their order will decide the sorting steps.
|
||||||
|
|
||||||
|
Any leftover keywords which do not use keys will be matched against the matches' file names.
|
||||||
|
Excluding certain keywords is also supported by adding negation sign (<b>-</b>) before the keywords.
|
||||||
|
|
||||||
<b>Internals:</b>
|
<b>Internals:</b>
|
||||||
First, it will filter uploads matching ANY of the supplied filter keys AND/OR special flags, if any.
|
First, it will filter uploads matching ANY of the supplied filter keys AND/OR special filter keys, if any.
|
||||||
Second, it will refine the matches using the supplied <b>name</b> keys, if any.
|
Second, it will refine the matches using the supplied <b>date</b> AND/OR <b>expiry</b> range keys, if any.
|
||||||
Third, it will sort the matches using the supplied <b>orderby</b> keys, if any.
|
Third, it will refine the matches using the leftover non-keyed keywords, if any.
|
||||||
|
Finally, it will sort the matches using the supplied <b>orderby</b> keys, if any.
|
||||||
|
|
||||||
<b>Examples:</b>
|
<b>Examples:</b>
|
||||||
Uploads from user with username "demo":
|
Uploads from user named "demo":
|
||||||
<code>user:demo</code>
|
<code>user:demo</code>
|
||||||
Uploads from users with username "John Doe" AND/OR "demo":
|
Uploads from users named "demo" AND/OR "John Doe" AND/OR non-registered users:
|
||||||
<code>user:John\\ Doe user:demo</code>
|
<code>user:demo user:"John Doe" user:-</code>
|
||||||
Uploads from IP "127.0.0.1" AND which upload names match "*.rar" OR "*.zip":
|
ALL uploads, including from non-registered users, but NOT the ones from user named "demo":
|
||||||
<code>ip:127.0.0.1 name:*.rar name:*.zip</code>
|
<code>-user:demo user:-</code>
|
||||||
Uploads from user with username "test" AND/OR from non-registered users:
|
Uploads from IP "127.0.0.1" AND which file names match "*.rar" OR "*.zip":
|
||||||
<code>user:test -user</code>
|
<code>ip:127.0.0.1 *.rar *.zip</code>
|
||||||
Sort results by "size" column in ascending and descending order respectively:
|
Uploads uploaded since "1 June 2019 00:00:00":
|
||||||
<code>orderby:expirydate</code>
|
<code>date:2019/06</code>
|
||||||
<code>user:demo orderby:size</code>
|
Uploads uploaded between "7 April 2020 00:00:00" and "7 April 2020 23:59:59":
|
||||||
<code>-user name:*.mp4 orderby:size:desc</code>
|
<code>date:2020/04/07-2020/04/07 23:59:59</code>
|
||||||
|
Uploads which file names match "*.gz" but NOT "*.tar.gz":
|
||||||
|
<code>*.gz -*.tar.gz</code>
|
||||||
|
Sort matches by "size" column in ascending and descending order respectively:
|
||||||
|
<code>user:"John Doe" orderby:size</code>
|
||||||
|
<code>*.mp4 user:- orderby:size:d</code>
|
||||||
|
|
||||||
|
<b>Friendly reminder:</b> This window can be scrolled up!
|
||||||
`.trim().replace(/^ {6}/gm, '').replace(/\n/g, '<br>')
|
`.trim().replace(/^ {6}/gm, '').replace(/\n/g, '<br>')
|
||||||
swal({ content })
|
|
||||||
|
swal({ content }).then(() => {
|
||||||
|
// Restore modal size
|
||||||
|
document.body.querySelector('.swal-overlay .swal-modal').classList.remove('is-expanded')
|
||||||
|
})
|
||||||
|
|
||||||
|
// Expand modal size
|
||||||
|
document.body.querySelector('.swal-overlay .swal-modal:not(.is-expanded)').classList.add('is-expanded')
|
||||||
}
|
}
|
||||||
|
|
||||||
page.filterUploads = element => {
|
page.filterUploads = element => {
|
||||||
@ -991,9 +1036,13 @@ page.viewUserUploads = (id, element) => {
|
|||||||
const user = page.cache.users[id]
|
const user = page.cache.users[id]
|
||||||
if (!user) return
|
if (!user) return
|
||||||
element.classList.add('is-loading')
|
element.classList.add('is-loading')
|
||||||
|
// Wrap username in quotes if it contains whitespaces
|
||||||
|
const username = user.username.includes(' ')
|
||||||
|
? `"${user.username}"`
|
||||||
|
: user.username
|
||||||
page.getUploads({
|
page.getUploads({
|
||||||
all: true,
|
all: true,
|
||||||
filters: `user:${user.username.replace(/ /g, '\\ ')}`,
|
filters: `user:${username}`,
|
||||||
trigger: document.querySelector('#itemManageUploads')
|
trigger: document.querySelector('#itemManageUploads')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -1682,7 +1731,9 @@ page.changeToken = (params = {}) => {
|
|||||||
swal({
|
swal({
|
||||||
title: 'Woohoo!',
|
title: 'Woohoo!',
|
||||||
text: 'Your token was successfully changed.',
|
text: 'Your token was successfully changed.',
|
||||||
icon: 'success'
|
icon: 'success',
|
||||||
|
buttons: false,
|
||||||
|
timer: 1500
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
axios.defaults.headers.common.token = response.data.token
|
axios.defaults.headers.common.token = response.data.token
|
||||||
localStorage[lsKeys.token] = response.data.token
|
localStorage[lsKeys.token] = response.data.token
|
||||||
@ -1806,10 +1857,10 @@ page.getUsers = (params = {}) => {
|
|||||||
<form class="prevent-default">
|
<form class="prevent-default">
|
||||||
<div class="field has-addons">
|
<div class="field has-addons">
|
||||||
<div class="control is-expanded">
|
<div class="control is-expanded">
|
||||||
<input id="filters" class="input is-small" type="text" placeholder="Filters (WIP)" value="${params.filters || ''}" disabled>
|
<input id="filters" class="input is-small" type="text" placeholder="Filters (WIP)" value="${page.escape(params.filters || '')}" disabled>
|
||||||
</div>
|
</div>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<button type="button" class="button is-small is-primary is-outlined" title="Help? (WIP)" data-action="upload-filters-help" disabled>
|
<button type="button" class="button is-small is-primary is-outlined" title="Help? (WIP)" data-action="user-filters-help" disabled>
|
||||||
<span class="icon">
|
<span class="icon">
|
||||||
<i class="icon-help-circled"></i>
|
<i class="icon-help-circled"></i>
|
||||||
</span>
|
</span>
|
||||||
@ -1851,11 +1902,11 @@ page.getUsers = (params = {}) => {
|
|||||||
const controls = `
|
const controls = `
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column has-text-left">
|
<div class="column has-text-left">
|
||||||
<a class="button is-small is-primary is-outlined" title="Create user (WIP)" data-action="create-user">
|
<a class="button is-small is-primary is-outlined" title="Create new user" data-action="create-user">
|
||||||
<span class="icon">
|
<span class="icon">
|
||||||
<i class="icon-plus"></i>
|
<i class="icon-plus"></i>
|
||||||
</span>
|
</span>
|
||||||
<span>Create user</span>
|
<span>Create new user</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="column has-text-right">
|
<div class="column has-text-right">
|
||||||
@ -1891,7 +1942,6 @@ page.getUsers = (params = {}) => {
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th><input id="selectAll" class="checkbox" type="checkbox" title="Select all" 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>Username</th>
|
<th>Username</th>
|
||||||
<th>Uploads</th>
|
<th>Uploads</th>
|
||||||
<th>Usage</th>
|
<th>Usage</th>
|
||||||
@ -1933,7 +1983,6 @@ page.getUsers = (params = {}) => {
|
|||||||
tr.dataset.id = user.id
|
tr.dataset.id = user.id
|
||||||
tr.innerHTML = `
|
tr.innerHTML = `
|
||||||
<td class="controls"><input type="checkbox" class="checkbox" title="Select" data-index="${i}" data-action="select"${selected ? ' checked' : ''}></td>
|
<td class="controls"><input type="checkbox" class="checkbox" title="Select" data-index="${i}" data-action="select"${selected ? ' checked' : ''}></td>
|
||||||
<th>${user.id}</th>
|
|
||||||
<th${enabled ? '' : ' class="is-linethrough"'}>${user.username}</td>
|
<th${enabled ? '' : ' class="is-linethrough"'}>${user.username}</td>
|
||||||
<th>${user.uploads}</th>
|
<th>${user.uploads}</th>
|
||||||
<td>${page.getPrettyBytes(user.usage)}</td>
|
<td>${page.getPrettyBytes(user.usage)}</td>
|
||||||
@ -2133,26 +2182,37 @@ page.editUser = id => {
|
|||||||
return swal('An error occurred!', response.data.description, 'error')
|
return swal('An error occurred!', response.data.description, 'error')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.data.password) {
|
let autoClose = true
|
||||||
const div = document.createElement('div')
|
const div = document.createElement('div')
|
||||||
div.innerHTML = `
|
|
||||||
<p><b>${user.username}</b>'s new password is:</p>
|
let displayName = user.username
|
||||||
<p><code>${response.data.password}</code></p>
|
if (response.data.update.username !== user.username) {
|
||||||
|
div.innerHTML += `<p>${user.username} was renamed into: <b>${response.data.update.username}</b>.</p>`
|
||||||
|
autoClose = false
|
||||||
|
displayName = response.data.update.username
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.data.update.password) {
|
||||||
|
div.innerHTML += `
|
||||||
|
<p>${displayName}'s new password is:</p>
|
||||||
|
<p><code>${response.data.update.password}</code></p>
|
||||||
`
|
`
|
||||||
|
autoClose = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.data.update.enabled !== user.enabled)
|
||||||
|
div.innerHTML += `<p>${displayName} has been ${response.data.update.enabled ? 'enabled' : 'disabled'}!</p>`
|
||||||
|
|
||||||
|
if (!div.innerHTML)
|
||||||
|
div.innerHTML = `<p>${displayName} was edited!</p>`
|
||||||
|
|
||||||
swal({
|
swal({
|
||||||
title: 'Success!',
|
title: 'Success!',
|
||||||
icon: 'success',
|
icon: 'success',
|
||||||
content: div
|
content: div,
|
||||||
|
buttons: !autoClose,
|
||||||
|
timer: autoClose ? 1500 : null
|
||||||
})
|
})
|
||||||
} else if (response.data.update && response.data.update.username !== user.username) {
|
|
||||||
swal('Success!', `${user.username} was renamed into: ${response.data.update.username}.`, 'success')
|
|
||||||
} else {
|
|
||||||
swal('Success!', 'The user was edited!', 'success', {
|
|
||||||
buttons: false,
|
|
||||||
timer: 1500
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
page.getUsers(page.views.users)
|
page.getUsers(page.views.users)
|
||||||
}).catch(page.onAxiosError)
|
}).catch(page.onAxiosError)
|
||||||
})
|
})
|
||||||
|
@ -43,9 +43,9 @@ ${headers.join(',\n')}
|
|||||||
}
|
}
|
||||||
|
|
||||||
page.getPrettyDate = date => {
|
page.getPrettyDate = date => {
|
||||||
return date.getFullYear() + '-' +
|
return date.getFullYear() + '/' +
|
||||||
(date.getMonth() < 9 ? '0' : '') + // month's index starts from zero
|
(date.getMonth() < 9 ? '0' : '') + // month's index starts from zero
|
||||||
(date.getMonth() + 1) + '-' +
|
(date.getMonth() + 1) + '/' +
|
||||||
(date.getDate() < 10 ? '0' : '') +
|
(date.getDate() < 10 ? '0' : '') +
|
||||||
date.getDate() + ' ' +
|
date.getDate() + ' ' +
|
||||||
(date.getHours() < 10 ? '0' : '') +
|
(date.getHours() < 10 ? '0' : '') +
|
||||||
@ -72,3 +72,56 @@ page.getPrettyBytes = num => {
|
|||||||
const pre = (si ? 'kMGTPEZY' : 'KMGTPEZY').charAt(exponent - 1) + (si ? '' : 'i')
|
const pre = (si ? 'kMGTPEZY' : 'KMGTPEZY').charAt(exponent - 1) + (si ? '' : 'i')
|
||||||
return `${neg}${numStr} ${pre}B`
|
return `${neg}${numStr} ${pre}B`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
page.escape = string => {
|
||||||
|
// MIT License
|
||||||
|
// Copyright(c) 2012-2013 TJ Holowaychuk
|
||||||
|
// Copyright(c) 2015 Andreas Lubbe
|
||||||
|
// Copyright(c) 2015 Tiancheng "Timothy" Gu
|
||||||
|
|
||||||
|
if (!string)
|
||||||
|
return string
|
||||||
|
|
||||||
|
const str = String(string)
|
||||||
|
const match = /["'&<>]/.exec(str)
|
||||||
|
|
||||||
|
if (!match)
|
||||||
|
return str
|
||||||
|
|
||||||
|
let escape
|
||||||
|
let html = ''
|
||||||
|
let index = 0
|
||||||
|
let lastIndex = 0
|
||||||
|
|
||||||
|
for (index = match.index; index < str.length; index++) {
|
||||||
|
switch (str.charCodeAt(index)) {
|
||||||
|
case 34: // "
|
||||||
|
escape = '"'
|
||||||
|
break
|
||||||
|
case 38: // &
|
||||||
|
escape = '&'
|
||||||
|
break
|
||||||
|
case 39: // '
|
||||||
|
escape = '''
|
||||||
|
break
|
||||||
|
case 60: // <
|
||||||
|
escape = '<'
|
||||||
|
break
|
||||||
|
case 62: // >
|
||||||
|
escape = '>'
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastIndex !== index)
|
||||||
|
html += str.substring(lastIndex, index)
|
||||||
|
|
||||||
|
lastIndex = index + 1
|
||||||
|
html += escape
|
||||||
|
}
|
||||||
|
|
||||||
|
return lastIndex !== index
|
||||||
|
? html + str.substring(lastIndex, index)
|
||||||
|
: html
|
||||||
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"1": "1587108179",
|
"1": "1587239207",
|
||||||
"2": "1581416390",
|
"2": "1581416390",
|
||||||
"3": "1581416390",
|
"3": "1581416390",
|
||||||
"4": "1581416390",
|
"4": "1581416390",
|
||||||
|
93
yarn.lock
93
yarn.lock
@ -144,7 +144,7 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.9.4.tgz#68a35e6b0319bbc014465be43828300113f2f2e8"
|
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.9.4.tgz#68a35e6b0319bbc014465be43828300113f2f2e8"
|
||||||
integrity sha512-bC49otXX6N0/VYhgOMh4gnP26E9xnDZK3TmbNpxYzzz9BQLBosQwfyOe9/cXUU3txYhTzLCbcqd5c8y/OmCjHA==
|
integrity sha512-bC49otXX6N0/VYhgOMh4gnP26E9xnDZK3TmbNpxYzzz9BQLBosQwfyOe9/cXUU3txYhTzLCbcqd5c8y/OmCjHA==
|
||||||
|
|
||||||
"@babel/runtime@^7.7.7", "@babel/runtime@^7.8.7":
|
"@babel/runtime@^7.7.7", "@babel/runtime@^7.9.2":
|
||||||
version "7.9.2"
|
version "7.9.2"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.9.2.tgz#d90df0583a3a252f09aaa619665367bae518db06"
|
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.9.2.tgz#d90df0583a3a252f09aaa619665367bae518db06"
|
||||||
integrity sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q==
|
integrity sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q==
|
||||||
@ -286,9 +286,9 @@
|
|||||||
integrity sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY=
|
integrity sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY=
|
||||||
|
|
||||||
"@types/node@*":
|
"@types/node@*":
|
||||||
version "13.11.1"
|
version "13.13.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.11.1.tgz#49a2a83df9d26daacead30d0ccc8762b128d53c7"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.13.0.tgz#30d2d09f623fe32cde9cb582c7a6eda2788ce4a8"
|
||||||
integrity sha512-eWQGP3qtxwL8FGneRrC5DwrJLGN4/dH1clNTuLfN81HCrxVtxRjygDTUoZJ5ASlDEeo0ppYFQjQIlXhtXpOn6g==
|
integrity sha512-WE4IOAC6r/yBZss1oQGM5zs2D7RuKR6Q+w+X2SouPofnWn+LbCqClRyhO3ZE7Ix8nmFgo/oVuuE01cJT2XB13A==
|
||||||
|
|
||||||
"@types/normalize-package-data@^2.4.0":
|
"@types/normalize-package-data@^2.4.0":
|
||||||
version "2.4.0"
|
version "2.4.0"
|
||||||
@ -930,9 +930,9 @@ buffer-from@^1.0.0:
|
|||||||
integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==
|
integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==
|
||||||
|
|
||||||
buffer@^5.5.0:
|
buffer@^5.5.0:
|
||||||
version "5.5.0"
|
version "5.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.5.0.tgz#9c3caa3d623c33dd1c7ef584b89b88bf9c9bc1ce"
|
resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.6.0.tgz#a31749dc7d81d84db08abf937b6b8c4033f62786"
|
||||||
integrity sha512-9FTEDjLjwoAkEwyMGDjYJQN2gfRgOKBKRfiglhvibGbpeeU/pQn1bJxQqm32OD/AIeEuHxU9roxXxg34Byp/Ww==
|
integrity sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==
|
||||||
dependencies:
|
dependencies:
|
||||||
base64-js "^1.0.2"
|
base64-js "^1.0.2"
|
||||||
ieee754 "^1.1.4"
|
ieee754 "^1.1.4"
|
||||||
@ -1037,14 +1037,14 @@ caniuse-api@^3.0.0:
|
|||||||
lodash.uniq "^4.5.0"
|
lodash.uniq "^4.5.0"
|
||||||
|
|
||||||
caniuse-db@^1.0.30001017:
|
caniuse-db@^1.0.30001017:
|
||||||
version "1.0.30001040"
|
version "1.0.30001042"
|
||||||
resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30001040.tgz#dd0f810b5d078175e6bbddc860eb4e7917885362"
|
resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30001042.tgz#ac3e6065c7c46fc0bdb0fb1a13d861af8399c3a4"
|
||||||
integrity sha512-rrwOYbMn4sTFNdqT4sD1gtrfOKr8CPyUEw3FMhlEYb4b+KHhBJY+a5NROsNTBDoABVHyW6v1EKG1iuJx+gosAg==
|
integrity sha512-2RKrB2hkLCW/8Uj32oaXj0O+N9ROo0/BF0EueWHwgs6AeeSiL+rCSsbICR3ayBJOZavgcFx65ZCw7QiafsoUFQ==
|
||||||
|
|
||||||
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001038, caniuse-lite@^1.0.30001039:
|
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001038, caniuse-lite@^1.0.30001039:
|
||||||
version "1.0.30001040"
|
version "1.0.30001042"
|
||||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001040.tgz#103fc8e6eb1d7397e95134cd0e996743353d58ea"
|
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001042.tgz#c91ec21ec2d270bd76dbc2ce261260c292b8c93c"
|
||||||
integrity sha512-Ep0tEPeI5wCvmJNrXjE3etgfI+lkl1fTDU6Y3ZH1mhrjkPlVI9W4pcKbMo+BQLpEWKVYYp2EmYaRsqpPC3k7lQ==
|
integrity sha512-igMQ4dlqnf4tWv0xjaaE02op9AJ2oQzXKjWf4EuAHFN694Uo9/EfPVIPJcmn2WkU9RqozCxx5e2KPcVClHDbDw==
|
||||||
|
|
||||||
caseless@~0.12.0:
|
caseless@~0.12.0:
|
||||||
version "0.12.0"
|
version "0.12.0"
|
||||||
@ -1183,9 +1183,9 @@ cli-cursor@^3.1.0:
|
|||||||
restore-cursor "^3.1.0"
|
restore-cursor "^3.1.0"
|
||||||
|
|
||||||
cli-width@^2.0.0:
|
cli-width@^2.0.0:
|
||||||
version "2.2.0"
|
version "2.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639"
|
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.1.tgz#b0433d0b4e9c847ef18868a4ef16fd5fc8271c48"
|
||||||
integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=
|
integrity sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==
|
||||||
|
|
||||||
cliui@^3.2.0:
|
cliui@^3.2.0:
|
||||||
version "3.2.0"
|
version "3.2.0"
|
||||||
@ -1973,9 +1973,9 @@ ee-first@1.1.1:
|
|||||||
integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
|
integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
|
||||||
|
|
||||||
electron-to-chromium@^1.3.390:
|
electron-to-chromium@^1.3.390:
|
||||||
version "1.3.403"
|
version "1.3.413"
|
||||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.403.tgz#c8bab4e2e72bf78bc28bad1cc355c061f9cc1918"
|
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.413.tgz#9c457a4165c7b42e59d66dff841063eb9bfe5614"
|
||||||
integrity sha512-JaoxV4RzdBAZOnsF4dAlZ2ijJW72MbqO5lNfOBHUWiBQl3Rwe+mk2RCUMrRI3rSClLJ8HSNQNqcry12H+0ZjFw==
|
integrity sha512-Jm1Rrd3siqYHO3jftZwDljL2LYQafj3Kki5r+udqE58d0i91SkjItVJ5RwlJn9yko8i7MOcoidVKjQlgSdd1hg==
|
||||||
|
|
||||||
emoji-regex@^7.0.1:
|
emoji-regex@^7.0.1:
|
||||||
version "7.0.3"
|
version "7.0.3"
|
||||||
@ -2265,11 +2265,11 @@ esprima@^4.0.0:
|
|||||||
integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
|
integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
|
||||||
|
|
||||||
esquery@^1.0.1:
|
esquery@^1.0.1:
|
||||||
version "1.2.0"
|
version "1.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.2.0.tgz#a010a519c0288f2530b3404124bfb5f02e9797fe"
|
resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.3.1.tgz#b78b5828aa8e214e29fb74c4d5b752e1c033da57"
|
||||||
integrity sha512-weltsSqdeWIX9G2qQZz7KlTRJdkkOCTPgLYJUz1Hacf48R4YOwGPHO3+ORfWedqJKbq5WQmsgK90n+pFLIKt/Q==
|
integrity sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
estraverse "^5.0.0"
|
estraverse "^5.1.0"
|
||||||
|
|
||||||
esrecurse@^4.1.0:
|
esrecurse@^4.1.0:
|
||||||
version "4.2.1"
|
version "4.2.1"
|
||||||
@ -2283,10 +2283,10 @@ estraverse@^4.1.0, estraverse@^4.1.1:
|
|||||||
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d"
|
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d"
|
||||||
integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==
|
integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==
|
||||||
|
|
||||||
estraverse@^5.0.0:
|
estraverse@^5.1.0:
|
||||||
version "5.0.0"
|
version "5.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.0.0.tgz#ac81750b482c11cca26e4b07e83ed8f75fbcdc22"
|
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.1.0.tgz#374309d39fd935ae500e7b92e8a6b4c720e59642"
|
||||||
integrity sha512-j3acdrMzqrxmJTNj5dbr1YbjacrYgAxVMeF0gK16E3j494mOe7xygM/ZLIguEQ0ETwAg2hlJCtHRGav+y0Ny5A==
|
integrity sha512-FyohXK+R0vE+y1nHLoBM7ZTyqRpqAlhdZHCWIWEviFLiGB8b04H6bQs8G+XTthacvT8VuwvteiP7RJSxMs8UEw==
|
||||||
|
|
||||||
esutils@^2.0.2:
|
esutils@^2.0.2:
|
||||||
version "2.0.3"
|
version "2.0.3"
|
||||||
@ -4040,10 +4040,10 @@ kind-of@^6.0.0, kind-of@^6.0.2:
|
|||||||
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd"
|
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd"
|
||||||
integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==
|
integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==
|
||||||
|
|
||||||
knex@^0.20.13:
|
knex@^0.20.15:
|
||||||
version "0.20.13"
|
version "0.20.15"
|
||||||
resolved "https://registry.yarnpkg.com/knex/-/knex-0.20.13.tgz#056c310d963f7efce1b3c7397576add1323f1146"
|
resolved "https://registry.yarnpkg.com/knex/-/knex-0.20.15.tgz#b7e9e1efd9cf35d214440d9439ed21153574679d"
|
||||||
integrity sha512-YVl//Te0G5suc+d9KyeI6WuhtgVlxu6HXYQB+WqrccFkSZAbHqlqZlUMogYG3UoVq69c3kiFbbxgUNkrO0PVfg==
|
integrity sha512-WHmvgfQfxA5v8pyb9zbskxCS1L1WmYgUbwBhHojlkmdouUOazvroUWlCr6KIKMQ8anXZh1NXOOtIUMnxENZG5Q==
|
||||||
dependencies:
|
dependencies:
|
||||||
colorette "1.1.0"
|
colorette "1.1.0"
|
||||||
commander "^4.1.1"
|
commander "^4.1.1"
|
||||||
@ -6419,9 +6419,9 @@ resolve-url@^0.2.1:
|
|||||||
integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=
|
integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=
|
||||||
|
|
||||||
resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.10.1, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.3.2, resolve@^1.4.0:
|
resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.10.1, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.3.2, resolve@^1.4.0:
|
||||||
version "1.15.1"
|
version "1.16.1"
|
||||||
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.15.1.tgz#27bdcdeffeaf2d6244b95bb0f9f4b4653451f3e8"
|
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.16.1.tgz#49fac5d8bacf1fd53f200fa51247ae736175832c"
|
||||||
integrity sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==
|
integrity sha512-rmAglCSqWWMrrBv/XM6sW0NuRFiKViw/W4d9EbC4pt+49H8JwHy+mcGmALTEg504AUDcLTvb1T2q3E9AnmY+ig==
|
||||||
dependencies:
|
dependencies:
|
||||||
path-parse "^1.0.6"
|
path-parse "^1.0.6"
|
||||||
|
|
||||||
@ -6527,6 +6527,11 @@ sax@^1.2.4, sax@~1.2.4:
|
|||||||
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
|
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
|
||||||
integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
|
integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
|
||||||
|
|
||||||
|
search-query-parser@^1.5.5:
|
||||||
|
version "1.5.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/search-query-parser/-/search-query-parser-1.5.5.tgz#dfd829f4fb567a9a3a5b70327576dff4d5be071d"
|
||||||
|
integrity sha512-8hKmRbHShAc+LJtUu7mu4L/oWVgICG1GCzWZtlR11Vl3T/v4aTGv+Ie2bPg+8ueJn+l8zMHCQhzrBNMoV/t2qw==
|
||||||
|
|
||||||
semver-diff@^3.1.1:
|
semver-diff@^3.1.1:
|
||||||
version "3.1.1"
|
version "3.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-3.1.1.tgz#05f77ce59f325e00e2706afd67bb506ddb1ca32b"
|
resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-3.1.1.tgz#05f77ce59f325e00e2706afd67bb506ddb1ca32b"
|
||||||
@ -6552,9 +6557,9 @@ semver@^6.0.0, semver@^6.1.0, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0:
|
|||||||
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
|
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
|
||||||
|
|
||||||
semver@^7.1.3:
|
semver@^7.1.3:
|
||||||
version "7.2.2"
|
version "7.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.2.2.tgz#d01432d74ed3010a20ffaf909d63a691520521cd"
|
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938"
|
||||||
integrity sha512-Zo84u6o2PebMSK3zjJ6Zp5wi8VnQZnEaCP13Ul/lt1ANsLACxnJxq4EEm1PY94/por1Hm9+7xpIswdS5AkieMA==
|
integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==
|
||||||
|
|
||||||
send@0.17.1:
|
send@0.17.1:
|
||||||
version "0.17.1"
|
version "0.17.1"
|
||||||
@ -7937,16 +7942,16 @@ yallist@^4.0.0:
|
|||||||
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
|
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
|
||||||
|
|
||||||
yaml@^1.7.2:
|
yaml@^1.7.2:
|
||||||
version "1.8.3"
|
version "1.9.1"
|
||||||
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.8.3.tgz#2f420fca58b68ce3a332d0ca64be1d191dd3f87a"
|
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.9.1.tgz#2df608ca571a0cf94e25e417e2795c08f48acdc5"
|
||||||
integrity sha512-X/v7VDnK+sxbQ2Imq4Jt2PRUsRsP7UcpSl3Llg6+NRRqWLIvxkMFYtH1FmvwNGYRKKPa+EPA4qDBlI9WVG1UKw==
|
integrity sha512-xbWX1ayUVoW8DPM8qxOBowac4XxSTi0mFLbiokRq880ViYglN+F3nJ4Dc2GdypXpykrknKS39d8I3lzFoHv1kA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.8.7"
|
"@babel/runtime" "^7.9.2"
|
||||||
|
|
||||||
yargs-parser@^18.1.1:
|
yargs-parser@^18.1.1:
|
||||||
version "18.1.2"
|
version "18.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.2.tgz#2f482bea2136dbde0861683abea7756d30b504f1"
|
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0"
|
||||||
integrity sha512-hlIPNR3IzC1YuL1c2UwwDKpXlNFBqD1Fswwh1khz5+d8Cq/8yc/Mn0i+rQXduu8hcrFKvO7Eryk+09NecTQAAQ==
|
integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
camelcase "^5.0.0"
|
camelcase "^5.0.0"
|
||||||
decamelize "^1.2.0"
|
decamelize "^1.2.0"
|
||||||
|
Loading…
Reference in New Issue
Block a user