"+e.toString()+"
",swal({title:"An error occurred!",icon:"error",content:a})},onAxiosError:function(e){console.error(e);var a={520:"Unknown Error",521:"Web Server Is Down",522:"Connection Timed Out",523:"Origin Is Unreachable",524:"A Timeout Occurred",525:"SSL Handshake Failed",526:"Invalid SSL Certificate",527:"Railgun Error",530:"Origin DNS Error"}[e.response.status]||e.response.statusText,t=e.response.data&&e.response.data.description?e.response.data.description:"There was an error with the request, please check the console for more information.";return swal(e.response.status+" "+a,t,"error")},preparePage:function(){page.token?page.verifyToken(page.token,!0):window.location="auth"},verifyToken:function(e,a){axios.post("api/tokens/verify",{token:e}).then((function(t){if(!1===t.data.success)return swal({title:"An error occurred!",text:t.data.description,icon:"error"}).then((function(){a&&(localStorage.removeItem(lsKeys.token),window.location="auth")}));axios.defaults.headers.common.token=e,localStorage[lsKeys.token]=e,page.token=e,page.username=t.data.username,page.permissions=t.data.permissions,page.prepareDashboard()})).catch(page.onAxiosError)},prepareDashboard:function(){page.dom=document.querySelector("#page"),page.dom.addEventListener("click",page.domClick,!0),page.dom.addEventListener("submit",(function(e){if(e.target&&e.target.classList.contains("prevent-default"))return e.preventDefault()}),!0),page.menusContainer=document.querySelector("#menu");for(var e=[{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},{selector:"#itemManageUploads",onclick:page.getUploads,params:{all:!0},group:"moderator"},{selector:"#itemStatistics",onclick:page.getStatistics,group:"admin"},{selector:"#itemManageUsers",onclick:page.getUsers,group:"admin"}],a=function(a){if(!e[a].group||page.permissions[e[a].group]){var t=document.querySelector(e[a].selector);t.addEventListener("click",(function(t){page.menusContainer.classList.contains("is-loading")||e[a].onclick.call(null,Object.assign({trigger:t.currentTarget},e[a].params||{}))})),t.classList.remove("is-hidden"),page.menus.push(t)}},t=0;t'+v.name+"
\n"+(v.appendix?""+v.appendix+" – ":"")+v.prettyBytes+"
\n "+(u&&v.prettyExpiryDate?'\nEXP: '+v.prettyExpiryDate+"
":"")+"\n\n | File | \n '+(void 0===e.album?""+(e.all?"User":"Album")+" | ":"")+"\nSize | \n "+(e.all?"IP | ":"")+"\nDate | \n "+(u?"Expiry date | ":"")+'\n\n |
---|
user:demo
\n\n Uploads from users with username either "John Doe" OR "demo":\n user:John\\ Doe user:demo
\n\n Uploads from IP "127.0.0.1" AND which upload names match "*.rar" OR "*.zip":\n ip:127.0.0.1 name:*.rar name:*.zip
\n\n Uploads from user with username "test" OR from non-registered users:\n user:test -user
\n '.trim().replace(/^ {6}/gm,"").replace(/\n/g,"You won't be able to recover "+t.replace(/^(\d*)(.*)/,"$1$2")+"!
";e.all&&(n+="\nWarning: You may be nuking "+(1===a?"an upload":"some uploads")+" by "+(1===a?"another user":"other users")+"!
");var s=document.createElement("div");s.innerHTML=n,swal({title:"Are you sure?",content:s,icon:"warning",dangerMode:!0,buttons:{cancel:!0,confirm:{text:"Yes, nuke "+(1===e.values.length?"it":"them")+"!",closeModal:!1}}}).then((function(n){n&&axios.post("api/upload/bulkdelete",{field:e.fields,values:e.values}).then((function(n){if(n){if(!1===n.data.success)return"No token provided"===n.data.description?page.verifyToken(page.token):swal("An error occurred!",n.data.description,"error");var s=Array.isArray(n.data.failed)?n.data.failed:[];s.length===e.values.length?swal("An error occurred!","Unable to delete any of the "+t+".","error"):s.length&&s.lengthYou are about to add '+t+" upload"+(1===t?"":"s")+' to an album.
\nIf an upload is already in an album, it will be moved.
\n \nID | \nName | \nFiles | \nCreated at | \nPublic link | \n\n |
---|
Max length is '+page.albumTitleMaxLength+' characters.
\nMax length is '+page.albumDescMaxLength+' characters.
\n\n | ID | \nUsername | \nUploads | \nUsage | \nGroup | \n\n |
---|
"+a.username+"'s new password is:
\n"+e.data.password+"
You will be disabling a user named "+page.cache.users[e].username+".
\nTheir files will remain.
\n ",swal({title:"Are you sure?",icon:"warning",content:t,dangerMode:!0,buttons:{cancel:!0,confirm:{text:"Yes, disable them!",closeModal:!1}}}).then((function(a){a&&axios.post("api/users/disable",{id:e}).then((function(a){if(a){if(!1===a.data.success)return"No token provided"===a.data.description?page.verifyToken(page.token):swal("An error occurred!",a.data.description,"error");swal("Success!",page.cache.users[e].username+" has been disabled.","success"),page.getUsers(page.views.users)}})).catch(page.onAxiosError)}))}},deleteUser:function(e){if(page.cache.users[e]){var a=document.createElement("div");a.innerHTML="\nYou will be deleting a user named "+page.cache.users[e].username+".
\n
Their files will remain, unless you choose otherwise.
\n ",swal({title:"Are you sure?",icon:"warning",content:a,dangerMode:!0,buttons:{cancel:!0,confirm:{text:"Yes, delete it!",closeModal:!1},purge:{text:"Yes, and the uploads too!",value:"purge",className:"swal-button--danger",closeModal:!1}}}).then((function(a){a&&axios.post("api/users/delete",{id:e,purge:"purge"===a}).then((function(a){if(a){if(!1===a.data.success){var t=Array.isArray(a.data.failed)?a.data.failed:[];return"No token provided"===a.data.description?page.verifyToken(page.token):t.length?swal("An error occurred!","Unable to delete "+t.length+" of the user's upload"+(1===t.length?"":"s")+".","error"):swal("An error occurred!",a.data.description,"error")}swal("Success!",page.cache.users[e].username+" has been deleted.","success"),page.getUsers(Object.assign({autoPage:!0},page.views.users))}})).catch(page.onAxiosError)}))}},paginate:function(e,a,t){t+=1;var n=Math.ceil(e/a),s="",i=function(e,a){for(var n=e;n<=a;++n)s+=''+n[s].toUpperCase()+" | \n\n |
---|
'+f.name+"
\n"+(f.appendix?""+f.appendix+" – ":"")+f.prettyBytes+"
\n "+(p&&f.prettyExpiryDate?'\nEXP: '+f.prettyExpiryDate+"
":"")+"\n\n | File | \n '+(void 0===e.album?""+(e.all?"User":"Album")+" | ":"")+"\nSize | \n "+(e.all?"IP | ":"")+"\nDate | \n "+(p?"Expiry date | ":"")+'\n\n |
---|
user:demo
\n\n Uploads from users with username either "John Doe" OR "demo":\n user:John\\ Doe user:demo
\n\n Uploads from IP "127.0.0.1" AND which upload names match "*.rar" OR "*.zip":\n ip:127.0.0.1 name:*.rar name:*.zip
\n\n Uploads from user with username "test" OR from non-registered users:\n user:test -user
\n '.trim().replace(/^ {6}/gm,"").replace(/\n/g,"You won't be able to recover "+t.replace(/^(\d*)(.*)/,"$1$2")+"!
";e.all&&(n+="\nWarning: You may be nuking "+(1===a?"an upload":"some uploads")+" by "+(1===a?"another user":"other users")+"!
");var s=document.createElement("div");s.innerHTML=n,swal({title:"Are you sure?",content:s,icon:"warning",dangerMode:!0,buttons:{cancel:!0,confirm:{text:"Yes, nuke "+(1===e.values.length?"it":"them")+"!",closeModal:!1}}}).then((function(n){n&&axios.post("api/upload/bulkdelete",{field:e.fields,values:e.values}).then((function(n){if(n){if(!1===n.data.success)return"No token provided"===n.data.description?page.verifyToken(page.token):swal("An error occurred!",n.data.description,"error");var s=Array.isArray(n.data.failed)?n.data.failed:[];s.length===e.values.length?swal("An error occurred!","Unable to delete any of the "+t+".","error"):s.length&&s.lengthYou are about to add '+t+" upload"+(1===t?"":"s")+' to an album.
\nIf an upload is already in an album, it will be moved.
\n \nID | \nName | \nFiles | \nCreated at | \nPublic link | \n\n |
---|
Max length is '+page.albumTitleMaxLength+' characters.
\nMax length is '+page.albumDescMaxLength+' characters.
\n\n | ID | \nUsername | \nUploads | \nUsage | \nGroup | \n\n |
---|
"+a.username+"'s new password is:
\n"+e.data.password+"
You will be disabling a user named "+page.cache.users[e].username+".
\nTheir files will remain.
\n ",swal({title:"Are you sure?",icon:"warning",content:t,dangerMode:!0,buttons:{cancel:!0,confirm:{text:"Yes, disable them!",closeModal:!1}}}).then((function(a){a&&axios.post("api/users/disable",{id:e}).then((function(a){if(a){if(!1===a.data.success)return"No token provided"===a.data.description?page.verifyToken(page.token):swal("An error occurred!",a.data.description,"error");swal("Success!",page.cache.users[e].username+" has been disabled.","success"),page.getUsers(page.views.users)}})).catch(page.onAxiosError)}))}},deleteUser:function(e){if(page.cache.users[e]){var a=document.createElement("div");a.innerHTML="\nYou will be deleting a user named "+page.cache.users[e].username+".
\n
Their files will remain, unless you choose otherwise.
\n ",swal({title:"Are you sure?",icon:"warning",content:a,dangerMode:!0,buttons:{cancel:!0,confirm:{text:"Yes, delete it!",closeModal:!1},purge:{text:"Yes, and the uploads too!",value:"purge",className:"swal-button--danger",closeModal:!1}}}).then((function(a){a&&axios.post("api/users/delete",{id:e,purge:"purge"===a}).then((function(a){if(a){if(!1===a.data.success){var t=Array.isArray(a.data.failed)?a.data.failed:[];return"No token provided"===a.data.description?page.verifyToken(page.token):t.length?swal("An error occurred!","Unable to delete "+t.length+" of the user's upload"+(1===t.length?"":"s")+".","error"):swal("An error occurred!",a.data.description,"error")}swal("Success!",page.cache.users[e].username+" has been deleted.","success"),page.getUsers(Object.assign({autoPage:!0},page.views.users))}})).catch(page.onAxiosError)}))}},paginate:function(e,a,t){t+=1;var n=Math.ceil(e/a),s="",i=function(e,a){for(var n=e;n<=a;++n)s+=''+n[s].toUpperCase()+" | \n\n |
---|
${error.toString()}
`\n return swal({\n title: 'An error occurred!',\n icon: 'error',\n content\n })\n}\n\n// Handler for Axios errors\npage.onAxiosError = error => {\n console.error(error)\n\n // Better Cloudflare errors\n const cloudflareErrors = {\n 520: 'Unknown Error',\n 521: 'Web Server Is Down',\n 522: 'Connection Timed Out',\n 523: 'Origin Is Unreachable',\n 524: 'A Timeout Occurred',\n 525: 'SSL Handshake Failed',\n 526: 'Invalid SSL Certificate',\n 527: 'Railgun Error',\n 530: 'Origin DNS Error'\n }\n\n const statusText = cloudflareErrors[error.response.status] || error.response.statusText\n const description = error.response.data && error.response.data.description\n ? error.response.data.description\n : 'There was an error with the request, please check the console for more information.'\n\n return swal(`${error.response.status} ${statusText}`, description, 'error')\n}\n\npage.preparePage = () => {\n if (page.token)\n page.verifyToken(page.token, true)\n else\n window.location = 'auth'\n}\n\npage.verifyToken = (token, reloadOnError) => {\n axios.post('api/tokens/verify', { token }).then(response => {\n if (response.data.success === false)\n return swal({\n title: 'An error occurred!',\n text: response.data.description,\n icon: 'error'\n }).then(() => {\n if (!reloadOnError) return\n localStorage.removeItem(lsKeys.token)\n window.location = 'auth'\n })\n\n axios.defaults.headers.common.token = token\n localStorage[lsKeys.token] = token\n\n page.token = token\n page.username = response.data.username\n page.permissions = response.data.permissions\n page.prepareDashboard()\n }).catch(page.onAxiosError)\n}\n\npage.prepareDashboard = () => {\n page.dom = document.querySelector('#page')\n\n // Capture all click events\n page.dom.addEventListener('click', page.domClick, true)\n\n // Capture all submit events\n page.dom.addEventListener('submit', event => {\n // Prevent default if necessary\n if (event.target && event.target.classList.contains('prevent-default'))\n return event.preventDefault()\n }, true)\n\n page.menusContainer = document.querySelector('#menu')\n\n // All item menus in the sidebar\n const itemMenus = [\n { selector: '#itemUploads', onclick: page.getUploads },\n { selector: '#itemDeleteUploadsByNames', onclick: page.deleteUploadsByNames },\n { selector: '#itemManageAlbums', onclick: page.getAlbums },\n { selector: '#itemManageToken', onclick: page.changeToken },\n { selector: '#itemChangePassword', onclick: page.changePassword },\n { selector: '#itemLogout', onclick: page.logout },\n { selector: '#itemManageUploads', onclick: page.getUploads, params: { all: true }, group: 'moderator' },\n { selector: '#itemStatistics', onclick: page.getStatistics, group: 'admin' },\n { selector: '#itemManageUsers', onclick: page.getUsers, group: 'admin' }\n ]\n\n for (let i = 0; i < itemMenus.length; i++) {\n // Skip item menu if not enough permission\n if (itemMenus[i].group && !page.permissions[itemMenus[i].group])\n continue\n\n // Add onclick event listener\n const item = document.querySelector(itemMenus[i].selector)\n item.addEventListener('click', event => {\n // This class name isn't actually being applied fast enough\n if (page.menusContainer.classList.contains('is-loading'))\n return\n\n // eslint-disable-next-line compat/compat\n itemMenus[i].onclick.call(null, Object.assign({\n trigger: event.currentTarget\n }, itemMenus[i].params || {}))\n })\n\n item.classList.remove('is-hidden')\n page.menus.push(item)\n }\n\n // If at least a moderator, show administration section\n if (page.permissions.moderator) {\n document.querySelector('#itemLabelAdmin').classList.remove('is-hidden')\n document.querySelector('#itemListAdmin').classList.remove('is-hidden')\n }\n\n // Update text of logout button\n document.querySelector('#itemLogout').innerHTML = `Logout ( ${page.username} )`\n\n // Finally display dashboard\n page.unhide()\n\n // Load albums sidebar\n page.getAlbumsSidebar()\n\n if (typeof page.prepareShareX === 'function')\n page.prepareShareX()\n}\n\npage.logout = params => {\n page.updateTrigger(params.trigger, 'active')\n localStorage.removeItem(lsKeys.token)\n window.location = 'auth'\n}\n\npage.updateTrigger = (trigger, newState) => {\n if (!trigger) return\n\n // Disable menus container when loading\n if (newState === 'loading')\n page.menusContainer.classList.add('is-loading')\n else\n page.menusContainer.classList.remove('is-loading')\n\n if (newState === 'loading') {\n trigger.classList.add('is-loading')\n } else if (newState === 'active') {\n if (trigger.parentNode.tagName !== 'LI')\n return\n for (let i = 0; i < page.menus.length; i++)\n page.menus[i].classList.remove('is-active')\n trigger.classList.remove('is-loading')\n trigger.classList.add('is-active')\n } else {\n trigger.classList.remove('is-loading')\n trigger.classList.remove('is-active')\n }\n}\n\npage.getItemID = element => {\n // This expects the item's parent to have the item's ID\n let parent = element.parentNode\n // If the element is part of a set of controls, use the container's parent instead\n if (element.parentNode.classList.contains('controls')) parent = parent.parentNode\n return parseInt(parent.dataset.id)\n}\n\npage.domClick = event => {\n // We are processing clicks this way to avoid using \"onclick\" attribute\n // Apparently we will need to use \"unsafe-inline\" for \"script-src\" directive\n // of Content Security Policy (CSP), if we want to use \"onclick\" attribute\n // Though I think that only applies to some browsers (?)\n // Of course it wouldn't have mattered if we didn't use CSP to begin with\n // Anyway, I personally would rather not use \"onclick\" attribute\n let element = event.target\n if (!element) return\n\n // Delegate click events to their A or BUTTON parents\n if (['I'].includes(element.tagName) && ['SPAN'].includes(element.parentNode.tagName))\n element = element.parentNode\n if (['SPAN'].includes(element.tagName) && ['A', 'BUTTON'].includes(element.parentNode.tagName))\n element = element.parentNode\n\n // Skip elements that have no action data\n if (!element.dataset || !element.dataset.action) return\n\n // Skip disabled elements\n if (element.hasAttribute('disabled')) return\n\n event.stopPropagation() // maybe necessary\n const id = page.getItemID(element)\n const action = element.dataset.action\n\n switch (action) {\n case 'view-list':\n return page.setUploadsView('list', element)\n case 'view-thumbs':\n return page.setUploadsView('thumbs', element)\n case 'clear-selection':\n return page.clearSelection()\n case 'add-selected-uploads-to-album':\n return page.addSelectedUploadsToAlbum()\n case 'select':\n return page.select(element, event)\n case 'select-all':\n return page.selectAll(element)\n case 'add-to-album':\n return page.addToAlbum(id)\n case 'delete-upload':\n return page.deleteUpload(id)\n case 'bulk-delete-uploads':\n return page.bulkDeleteUploads()\n case 'display-preview':\n return page.displayPreview(id)\n case 'submit-album':\n return page.submitAlbum(element)\n case 'edit-album':\n return page.editAlbum(id)\n case 'delete-album':\n return page.deleteAlbum(id)\n case 'get-new-token':\n return page.getNewToken(element)\n case 'edit-user':\n return page.editUser(id)\n case 'disable-user':\n return page.disableUser(id)\n case 'delete-user':\n return page.deleteUser(id)\n case 'filters-help':\n return page.filtersHelp(element)\n case 'filter-uploads':\n return page.filterUploads(element)\n case 'view-user-uploads':\n return page.viewUserUploads(id, element)\n case 'page-ellipsis':\n return page.focusJumpToPage()\n case 'page-prev':\n case 'page-next':\n case 'page-goto':\n case 'jump-to-page':\n return page.switchPage(action, element)\n }\n}\n\npage.fadeAndScroll = content => {\n if (page.fadingIn) {\n clearTimeout(page.fadingIn)\n page.dom.classList.remove('fade-in')\n }\n\n page.dom.classList.add('fade-in')\n page.fadingIn = setTimeout(() => {\n page.dom.classList.remove('fade-in')\n }, 500)\n\n page.dom.scrollIntoView({\n behavior: 'smooth',\n block: 'start',\n inline: 'nearest'\n })\n}\n\npage.switchPage = (action, element) => {\n // eslint-disable-next-line compat/compat\n const params = Object.assign({\n trigger: element\n }, page.views[page.currentView])\n const func = page.currentView === 'users' ? page.getUsers : page.getUploads\n switch (action) {\n case 'page-prev':\n params.pageNum = page.views[page.currentView].pageNum - 1\n if (params.pageNum < 0)\n return swal('An error occurred!', 'This is already the first page.', 'error')\n return func(params)\n case 'page-next':\n params.pageNum = page.views[page.currentView].pageNum + 1\n return func(params)\n case 'page-goto':\n params.pageNum = parseInt(element.dataset.goto)\n return func(params)\n case 'jump-to-page': {\n const jumpToPage = document.querySelector('#jumpToPage')\n if (!jumpToPage.checkValidity()) return\n const parsed = parseInt(jumpToPage.value)\n params.pageNum = isNaN(parsed) ? 0 : (parsed - 1)\n if (params.pageNum < 0) params.pageNum = 0\n return func(params)\n }\n }\n}\n\npage.focusJumpToPage = () => {\n const element = document.querySelector('#jumpToPage')\n if (!element) return\n element.focus()\n element.select()\n}\n\npage.getUploads = (params = {}) => {\n if (params === undefined)\n params = {}\n\n if ((params.all || params.filters) && !page.permissions.moderator)\n return swal('An error occurred!', 'You can not do this!', 'error')\n\n page.updateTrigger(params.trigger, 'loading')\n\n if (typeof params.pageNum !== 'number' || params.pageNum < 0)\n params.pageNum = 0\n\n const url = params.album !== undefined\n ? `api/album/${params.album}/${params.pageNum}`\n : `api/uploads/${params.pageNum}`\n\n const headers = {\n all: params.all ? '1' : '',\n filters: params.filters || ''\n }\n\n axios.get(url, { headers }).then(response => {\n if (response.data.success === false)\n if (response.data.description === 'No token provided') {\n return page.verifyToken(page.token)\n } else {\n page.updateTrigger(params.trigger)\n return swal('An error occurred!', response.data.description, 'error')\n }\n\n const files = response.data.files\n if (params.pageNum && (files.length === 0))\n if (params.autoPage) {\n params.pageNum = Math.ceil(response.data.count / 25) - 1\n return page.getUploads(params)\n } else {\n page.updateTrigger(params.trigger)\n return swal('An error occurred!', `There are no more uploads to populate page ${params.pageNum + 1}.`, 'error')\n }\n\n page.currentView = params.all ? 'uploadsAll' : 'uploads'\n page.cache.uploads = {}\n\n const albums = response.data.albums\n const users = response.data.users\n const basedomain = response.data.basedomain\n const pagination = page.paginate(response.data.count, 25, params.pageNum)\n\n let filter = ''\n if (params.all)\n filter = `\n ${upload.name}
\n${upload.appendix ? `${upload.appendix} – ` : ''}${upload.prettyBytes}
\n ${hasExpiryDateColumn && upload.prettyExpiryDate ? `\nEXP: ${upload.prettyExpiryDate}
` : ''}\n\n | File | \n ${params.album === undefined ? `${params.all ? 'User' : 'Album'} | ` : ''}\nSize | \n ${params.all ? 'IP | ' : ''}\nDate | \n ${hasExpiryDateColumn ? 'Expiry date | ' : ''}\n\n |
---|
user:demo
\n\n Uploads from users with username either \"John Doe\" OR \"demo\":\n user:John\\\\ Doe user:demo
\n\n Uploads from IP \"127.0.0.1\" AND which upload names match \"*.rar\" OR \"*.zip\":\n ip:127.0.0.1 name:*.rar name:*.zip
\n\n Uploads from user with username \"test\" OR from non-registered users:\n user:test -user
\n `.trim().replace(/^ {6}/gm, '').replace(/\\n/g, 'You won't be able to recover ${boldObjective}!
`\n\n if (params.all) {\n const obj1 = count === 1 ? 'an upload' : 'some uploads'\n const obj2 = count === 1 ? 'another user' : 'other users'\n text += `\\nWarning: You may be nuking ${obj1} by ${obj2}!
`\n }\n\n const content = document.createElement('div')\n content.innerHTML = text\n\n swal({\n title: 'Are you sure?',\n content,\n icon: 'warning',\n dangerMode: true,\n buttons: {\n cancel: true,\n confirm: {\n text: `Yes, nuke ${params.values.length === 1 ? 'it' : 'them'}!`,\n closeModal: false\n }\n }\n }).then(proceed => {\n if (!proceed) return\n\n axios.post('api/upload/bulkdelete', {\n field: params.fields,\n values: params.values\n }).then(response => {\n if (!response) return\n\n if (response.data.success === false)\n if (response.data.description === 'No token provided') {\n return page.verifyToken(page.token)\n } else {\n return swal('An error occurred!', response.data.description, 'error')\n }\n\n const failed = Array.isArray(response.data.failed) ? response.data.failed : []\n if (failed.length === params.values.length)\n swal('An error occurred!', `Unable to delete any of the ${objective}.`, 'error')\n else if (failed.length && failed.length < params.values.length)\n swal('Warning!', `From ${objective}, unable to delete ${failed.length} of them.`, 'warning')\n else\n swal('Deleted!', `${objective} ${count === 1 ? 'has' : 'have'} been deleted.`, 'success')\n\n if (typeof params.cb === 'function')\n params.cb(failed)\n }).catch(page.onAxiosError)\n })\n}\n\npage.addSelectedUploadsToAlbum = () => {\n if (page.currentView !== 'uploads')\n return\n\n const count = page.selected[page.currentView].length\n if (!count)\n return swal('An error occurred!', 'You have not selected any uploads.', 'error')\n\n page.addUploadsToAlbum(page.selected[page.currentView], failed => {\n if (!failed) return\n if (failed.length)\n page.selected[page.currentView] = page.selected[page.currentView].filter(id => {\n return failed.includes(id)\n })\n else\n page.selected[page.currentView] = []\n\n localStorage[lsKeys.selected[page.currentView]] = JSON.stringify(page.selected[page.currentView])\n page.getUploads(page.views[page.currentView])\n })\n}\n\npage.addToAlbum = id => {\n page.addUploadsToAlbum([id], failed => {\n if (!failed) return\n page.getUploads(page.views[page.currentView])\n })\n}\n\npage.addUploadsToAlbum = (ids, callback) => {\n const count = ids.length\n\n const content = document.createElement('div')\n content.innerHTML = `\nYou are about to add ${count} upload${count === 1 ? '' : 's'} to an album.
\nIf an upload is already in an album, it will be moved.
\nID | \nName | \nFiles | \nCreated at | \nPublic link | \n\n |
---|
Max length is ${page.albumTitleMaxLength} characters.
\nMax length is ${page.albumDescMaxLength} characters.
\n\n | ID | \nUsername | \nUploads | \nUsage | \nGroup | \n\n |
---|
${user.username}'s new password is:
\n${response.data.password}
You will be disabling a user named ${page.cache.users[id].username}.
\nTheir files will remain.
\n `\n\n swal({\n title: 'Are you sure?',\n icon: 'warning',\n content,\n dangerMode: true,\n buttons: {\n cancel: true,\n confirm: {\n text: 'Yes, disable them!',\n closeModal: false\n }\n }\n }).then(proceed => {\n if (!proceed) return\n\n axios.post('api/users/disable', { id }).then(response => {\n if (!response) return\n\n if (response.data.success === false)\n if (response.data.description === 'No token provided')\n return page.verifyToken(page.token)\n else\n return swal('An error occurred!', response.data.description, 'error')\n\n swal('Success!', `${page.cache.users[id].username} has been disabled.`, 'success')\n page.getUsers(page.views.users)\n }).catch(page.onAxiosError)\n })\n}\n\npage.deleteUser = id => {\n const user = page.cache.users[id]\n if (!user) return\n\n const content = document.createElement('div')\n content.innerHTML = `\nYou will be deleting a user named ${page.cache.users[id].username}.
\n
Their files will remain, unless you choose otherwise.
\n `\n\n swal({\n title: 'Are you sure?',\n icon: 'warning',\n content,\n dangerMode: true,\n buttons: {\n cancel: true,\n confirm: {\n text: 'Yes, delete it!',\n closeModal: false\n },\n purge: {\n text: 'Yes, and the uploads too!',\n value: 'purge',\n className: 'swal-button--danger',\n closeModal: false\n }\n }\n }).then(proceed => {\n if (!proceed) return\n\n axios.post('api/users/delete', {\n id,\n purge: proceed === 'purge'\n }).then(response => {\n if (!response) return\n\n if (response.data.success === false) {\n const failed = Array.isArray(response.data.failed)\n ? response.data.failed\n : []\n\n if (response.data.description === 'No token provided')\n return page.verifyToken(page.token)\n else if (failed.length)\n return swal('An error occurred!', `Unable to delete ${failed.length} of the user's upload${failed.length === 1 ? '' : 's'}.`, 'error')\n else\n return swal('An error occurred!', response.data.description, 'error')\n }\n\n swal('Success!', `${page.cache.users[id].username} has been deleted.`, 'success')\n\n // Reload users list\n // eslint-disable-next-line compat/compat\n page.getUsers(Object.assign({\n autoPage: true\n }, page.views.users))\n }).catch(page.onAxiosError)\n })\n}\n\n// Roughly based on https://github.com/mayuska/pagination/blob/master/index.js\npage.paginate = (totalItems, itemsPerPage, currentPage) => {\n currentPage = currentPage + 1\n const step = 3\n const numPages = Math.ceil(totalItems / itemsPerPage)\n\n let template = ''\n const elementsToShow = step * 2\n const add = {\n pageNum (start, end) {\n for (let i = start; i <= end; ++i)\n template += `${keys[i].toUpperCase()} | \n\n |
---|
${error.toString()}
`\n return swal({\n title: 'An error occurred!',\n icon: 'error',\n content\n })\n}\n\n// Handler for Axios errors\npage.onAxiosError = error => {\n console.error(error)\n\n // Better Cloudflare errors\n const cloudflareErrors = {\n 520: 'Unknown Error',\n 521: 'Web Server Is Down',\n 522: 'Connection Timed Out',\n 523: 'Origin Is Unreachable',\n 524: 'A Timeout Occurred',\n 525: 'SSL Handshake Failed',\n 526: 'Invalid SSL Certificate',\n 527: 'Railgun Error',\n 530: 'Origin DNS Error'\n }\n\n const statusText = cloudflareErrors[error.response.status] || error.response.statusText\n const description = error.response.data && error.response.data.description\n ? error.response.data.description\n : 'There was an error with the request, please check the console for more information.'\n\n return swal(`${error.response.status} ${statusText}`, description, 'error')\n}\n\npage.preparePage = () => {\n if (page.token)\n page.verifyToken(page.token, true)\n else\n window.location = 'auth'\n}\n\npage.verifyToken = (token, reloadOnError) => {\n axios.post('api/tokens/verify', { token }).then(response => {\n if (response.data.success === false)\n return swal({\n title: 'An error occurred!',\n text: response.data.description,\n icon: 'error'\n }).then(() => {\n if (!reloadOnError) return\n localStorage.removeItem(lsKeys.token)\n window.location = 'auth'\n })\n\n axios.defaults.headers.common.token = token\n localStorage[lsKeys.token] = token\n\n page.token = token\n page.username = response.data.username\n page.permissions = response.data.permissions\n page.prepareDashboard()\n }).catch(page.onAxiosError)\n}\n\npage.prepareDashboard = () => {\n page.dom = document.querySelector('#page')\n\n // Capture all click events\n page.dom.addEventListener('click', page.domClick, true)\n\n // Capture all submit events\n page.dom.addEventListener('submit', event => {\n // Prevent default if necessary\n if (event.target && event.target.classList.contains('prevent-default'))\n return event.preventDefault()\n }, true)\n\n page.menusContainer = document.querySelector('#menu')\n\n // All item menus in the sidebar\n const itemMenus = [\n { selector: '#itemUploads', onclick: page.getUploads },\n { selector: '#itemDeleteUploadsByNames', onclick: page.deleteUploadsByNames },\n { selector: '#itemManageAlbums', onclick: page.getAlbums },\n { selector: '#itemManageToken', onclick: page.changeToken },\n { selector: '#itemChangePassword', onclick: page.changePassword },\n { selector: '#itemLogout', onclick: page.logout },\n { selector: '#itemManageUploads', onclick: page.getUploads, params: { all: true }, group: 'moderator' },\n { selector: '#itemStatistics', onclick: page.getStatistics, group: 'admin' },\n { selector: '#itemManageUsers', onclick: page.getUsers, group: 'admin' }\n ]\n\n for (let i = 0; i < itemMenus.length; i++) {\n // Skip item menu if not enough permission\n if (itemMenus[i].group && !page.permissions[itemMenus[i].group])\n continue\n\n // Add onclick event listener\n const item = document.querySelector(itemMenus[i].selector)\n item.addEventListener('click', event => {\n // This class name isn't actually being applied fast enough\n if (page.menusContainer.classList.contains('is-loading'))\n return\n\n // eslint-disable-next-line compat/compat\n itemMenus[i].onclick.call(null, Object.assign({\n trigger: event.currentTarget\n }, itemMenus[i].params || {}))\n })\n\n item.classList.remove('is-hidden')\n page.menus.push(item)\n }\n\n // If at least a moderator, show administration section\n if (page.permissions.moderator) {\n document.querySelector('#itemLabelAdmin').classList.remove('is-hidden')\n document.querySelector('#itemListAdmin').classList.remove('is-hidden')\n }\n\n // Update text of logout button\n document.querySelector('#itemLogout').innerHTML = `Logout ( ${page.username} )`\n\n // Finally display dashboard\n page.unhide()\n\n // Load albums sidebar\n page.getAlbumsSidebar()\n\n if (typeof page.prepareShareX === 'function')\n page.prepareShareX()\n}\n\npage.logout = params => {\n page.updateTrigger(params.trigger, 'active')\n localStorage.removeItem(lsKeys.token)\n window.location = 'auth'\n}\n\npage.updateTrigger = (trigger, newState) => {\n if (!trigger) return\n\n // Disable menus container when loading\n if (newState === 'loading')\n page.menusContainer.classList.add('is-loading')\n else\n page.menusContainer.classList.remove('is-loading')\n\n if (newState === 'loading') {\n trigger.classList.add('is-loading')\n } else if (newState === 'active') {\n if (trigger.parentNode.tagName !== 'LI')\n return\n for (let i = 0; i < page.menus.length; i++)\n page.menus[i].classList.remove('is-active')\n trigger.classList.remove('is-loading')\n trigger.classList.add('is-active')\n } else {\n trigger.classList.remove('is-loading')\n trigger.classList.remove('is-active')\n }\n}\n\npage.getItemID = element => {\n // This expects the item's parent to have the item's ID\n let parent = element.parentNode\n // If the element is part of a set of controls, use the container's parent instead\n if (element.parentNode.classList.contains('controls')) parent = parent.parentNode\n return parseInt(parent.dataset.id)\n}\n\npage.domClick = event => {\n // We are processing clicks this way to avoid using \"onclick\" attribute\n // Apparently we will need to use \"unsafe-inline\" for \"script-src\" directive\n // of Content Security Policy (CSP), if we want to use \"onclick\" attribute\n // Though I think that only applies to some browsers (?)\n // Of course it wouldn't have mattered if we didn't use CSP to begin with\n // Anyway, I personally would rather not use \"onclick\" attribute\n let element = event.target\n if (!element) return\n\n // Delegate click events to their A or BUTTON parents\n if (['I'].includes(element.tagName) && ['SPAN'].includes(element.parentNode.tagName))\n element = element.parentNode\n if (['SPAN'].includes(element.tagName) && ['A', 'BUTTON'].includes(element.parentNode.tagName))\n element = element.parentNode\n\n // Skip elements that have no action data\n if (!element.dataset || !element.dataset.action) return\n\n // Skip disabled elements\n if (element.hasAttribute('disabled')) return\n\n event.stopPropagation() // maybe necessary\n const id = page.getItemID(element)\n const action = element.dataset.action\n\n switch (action) {\n case 'view-list':\n return page.setUploadsView('list', element)\n case 'view-thumbs':\n return page.setUploadsView('thumbs', element)\n case 'clear-selection':\n return page.clearSelection()\n case 'add-selected-uploads-to-album':\n return page.addSelectedUploadsToAlbum()\n case 'select':\n return page.select(element, event)\n case 'select-all':\n return page.selectAll(element)\n case 'add-to-album':\n return page.addToAlbum(id)\n case 'delete-upload':\n return page.deleteUpload(id)\n case 'bulk-delete-uploads':\n return page.bulkDeleteUploads()\n case 'display-preview':\n return page.displayPreview(id)\n case 'submit-album':\n return page.submitAlbum(element)\n case 'edit-album':\n return page.editAlbum(id)\n case 'delete-album':\n return page.deleteAlbum(id)\n case 'get-new-token':\n return page.getNewToken(element)\n case 'edit-user':\n return page.editUser(id)\n case 'disable-user':\n return page.disableUser(id)\n case 'delete-user':\n return page.deleteUser(id)\n case 'filters-help':\n return page.filtersHelp(element)\n case 'filter-uploads':\n return page.filterUploads(element)\n case 'view-user-uploads':\n return page.viewUserUploads(id, element)\n case 'page-ellipsis':\n return page.focusJumpToPage()\n case 'page-prev':\n case 'page-next':\n case 'page-goto':\n case 'jump-to-page':\n return page.switchPage(action, element)\n }\n}\n\npage.fadeAndScroll = content => {\n if (page.fadingIn) {\n clearTimeout(page.fadingIn)\n page.dom.classList.remove('fade-in')\n }\n\n page.dom.classList.add('fade-in')\n page.fadingIn = setTimeout(() => {\n page.dom.classList.remove('fade-in')\n }, 500)\n\n page.dom.scrollIntoView({\n behavior: 'smooth',\n block: 'start',\n inline: 'nearest'\n })\n}\n\npage.switchPage = (action, element) => {\n // eslint-disable-next-line compat/compat\n const params = Object.assign({\n trigger: element\n }, page.views[page.currentView])\n const func = page.currentView === 'users' ? page.getUsers : page.getUploads\n switch (action) {\n case 'page-prev':\n params.pageNum = page.views[page.currentView].pageNum - 1\n if (params.pageNum < 0)\n return swal('An error occurred!', 'This is already the first page.', 'error')\n return func(params)\n case 'page-next':\n params.pageNum = page.views[page.currentView].pageNum + 1\n return func(params)\n case 'page-goto':\n params.pageNum = parseInt(element.dataset.goto)\n return func(params)\n case 'jump-to-page': {\n const jumpToPage = document.querySelector('#jumpToPage')\n if (!jumpToPage.checkValidity()) return\n const parsed = parseInt(jumpToPage.value)\n params.pageNum = isNaN(parsed) ? 0 : (parsed - 1)\n if (params.pageNum < 0) params.pageNum = 0\n return func(params)\n }\n }\n}\n\npage.focusJumpToPage = () => {\n const element = document.querySelector('#jumpToPage')\n if (!element) return\n element.focus()\n element.select()\n}\n\npage.getUploads = (params = {}) => {\n if (params === undefined)\n params = {}\n\n if ((params.all || params.filters) && !page.permissions.moderator)\n return swal('An error occurred!', 'You can not do this!', 'error')\n\n page.updateTrigger(params.trigger, 'loading')\n\n if (typeof params.pageNum !== 'number' || params.pageNum < 0)\n params.pageNum = 0\n\n const url = params.album !== undefined\n ? `api/album/${params.album}/${params.pageNum}`\n : `api/uploads/${params.pageNum}`\n\n const headers = {\n all: params.all ? '1' : '',\n filters: params.filters || ''\n }\n\n axios.get(url, { headers }).then(response => {\n if (response.data.success === false)\n if (response.data.description === 'No token provided') {\n return page.verifyToken(page.token)\n } else {\n page.updateTrigger(params.trigger)\n return swal('An error occurred!', response.data.description, 'error')\n }\n\n const pages = Math.ceil(response.data.count / 25)\n const files = response.data.files\n if (params.pageNum && (files.length === 0))\n if (params.autoPage) {\n params.pageNum = pages - 1\n return page.getUploads(params)\n } else {\n page.updateTrigger(params.trigger)\n return swal('An error occurred!', `There are no more uploads to populate page ${params.pageNum + 1}.`, 'error')\n }\n\n page.currentView = params.all ? 'uploadsAll' : 'uploads'\n page.cache.uploads = {}\n\n const albums = response.data.albums\n const users = response.data.users\n const basedomain = response.data.basedomain\n const pagination = page.paginate(response.data.count, 25, params.pageNum)\n\n let filter = ''\n if (params.all)\n filter = `\n ${upload.name}
\n${upload.appendix ? `${upload.appendix} – ` : ''}${upload.prettyBytes}
\n ${hasExpiryDateColumn && upload.prettyExpiryDate ? `\nEXP: ${upload.prettyExpiryDate}
` : ''}\n\n | File | \n ${params.album === undefined ? `${params.all ? 'User' : 'Album'} | ` : ''}\nSize | \n ${params.all ? 'IP | ' : ''}\nDate | \n ${hasExpiryDateColumn ? 'Expiry date | ' : ''}\n\n |
---|
user:demo
\n\n Uploads from users with username either \"John Doe\" OR \"demo\":\n user:John\\\\ Doe user:demo
\n\n Uploads from IP \"127.0.0.1\" AND which upload names match \"*.rar\" OR \"*.zip\":\n ip:127.0.0.1 name:*.rar name:*.zip
\n\n Uploads from user with username \"test\" OR from non-registered users:\n user:test -user
\n `.trim().replace(/^ {6}/gm, '').replace(/\\n/g, 'You won't be able to recover ${boldObjective}!
`\n\n if (params.all) {\n const obj1 = count === 1 ? 'an upload' : 'some uploads'\n const obj2 = count === 1 ? 'another user' : 'other users'\n text += `\\nWarning: You may be nuking ${obj1} by ${obj2}!
`\n }\n\n const content = document.createElement('div')\n content.innerHTML = text\n\n swal({\n title: 'Are you sure?',\n content,\n icon: 'warning',\n dangerMode: true,\n buttons: {\n cancel: true,\n confirm: {\n text: `Yes, nuke ${params.values.length === 1 ? 'it' : 'them'}!`,\n closeModal: false\n }\n }\n }).then(proceed => {\n if (!proceed) return\n\n axios.post('api/upload/bulkdelete', {\n field: params.fields,\n values: params.values\n }).then(response => {\n if (!response) return\n\n if (response.data.success === false)\n if (response.data.description === 'No token provided') {\n return page.verifyToken(page.token)\n } else {\n return swal('An error occurred!', response.data.description, 'error')\n }\n\n const failed = Array.isArray(response.data.failed) ? response.data.failed : []\n if (failed.length === params.values.length)\n swal('An error occurred!', `Unable to delete any of the ${objective}.`, 'error')\n else if (failed.length && failed.length < params.values.length)\n swal('Warning!', `From ${objective}, unable to delete ${failed.length} of them.`, 'warning')\n else\n swal('Deleted!', `${objective} ${count === 1 ? 'has' : 'have'} been deleted.`, 'success')\n\n if (typeof params.cb === 'function')\n params.cb(failed)\n }).catch(page.onAxiosError)\n })\n}\n\npage.addSelectedUploadsToAlbum = () => {\n if (page.currentView !== 'uploads')\n return\n\n const count = page.selected[page.currentView].length\n if (!count)\n return swal('An error occurred!', 'You have not selected any uploads.', 'error')\n\n page.addUploadsToAlbum(page.selected[page.currentView], failed => {\n if (!failed) return\n if (failed.length)\n page.selected[page.currentView] = page.selected[page.currentView].filter(id => {\n return failed.includes(id)\n })\n else\n page.selected[page.currentView] = []\n\n localStorage[lsKeys.selected[page.currentView]] = JSON.stringify(page.selected[page.currentView])\n page.getUploads(page.views[page.currentView])\n })\n}\n\npage.addToAlbum = id => {\n page.addUploadsToAlbum([id], failed => {\n if (!failed) return\n page.getUploads(page.views[page.currentView])\n })\n}\n\npage.addUploadsToAlbum = (ids, callback) => {\n const count = ids.length\n\n const content = document.createElement('div')\n content.innerHTML = `\nYou are about to add ${count} upload${count === 1 ? '' : 's'} to an album.
\nIf an upload is already in an album, it will be moved.
\nID | \nName | \nFiles | \nCreated at | \nPublic link | \n\n |
---|
Max length is ${page.albumTitleMaxLength} characters.
\nMax length is ${page.albumDescMaxLength} characters.
\n\n | ID | \nUsername | \nUploads | \nUsage | \nGroup | \n\n |
---|
${user.username}'s new password is:
\n${response.data.password}
You will be disabling a user named ${page.cache.users[id].username}.
\nTheir files will remain.
\n `\n\n swal({\n title: 'Are you sure?',\n icon: 'warning',\n content,\n dangerMode: true,\n buttons: {\n cancel: true,\n confirm: {\n text: 'Yes, disable them!',\n closeModal: false\n }\n }\n }).then(proceed => {\n if (!proceed) return\n\n axios.post('api/users/disable', { id }).then(response => {\n if (!response) return\n\n if (response.data.success === false)\n if (response.data.description === 'No token provided')\n return page.verifyToken(page.token)\n else\n return swal('An error occurred!', response.data.description, 'error')\n\n swal('Success!', `${page.cache.users[id].username} has been disabled.`, 'success')\n page.getUsers(page.views.users)\n }).catch(page.onAxiosError)\n })\n}\n\npage.deleteUser = id => {\n const user = page.cache.users[id]\n if (!user) return\n\n const content = document.createElement('div')\n content.innerHTML = `\nYou will be deleting a user named ${page.cache.users[id].username}.
\n
Their files will remain, unless you choose otherwise.
\n `\n\n swal({\n title: 'Are you sure?',\n icon: 'warning',\n content,\n dangerMode: true,\n buttons: {\n cancel: true,\n confirm: {\n text: 'Yes, delete it!',\n closeModal: false\n },\n purge: {\n text: 'Yes, and the uploads too!',\n value: 'purge',\n className: 'swal-button--danger',\n closeModal: false\n }\n }\n }).then(proceed => {\n if (!proceed) return\n\n axios.post('api/users/delete', {\n id,\n purge: proceed === 'purge'\n }).then(response => {\n if (!response) return\n\n if (response.data.success === false) {\n const failed = Array.isArray(response.data.failed)\n ? response.data.failed\n : []\n\n if (response.data.description === 'No token provided')\n return page.verifyToken(page.token)\n else if (failed.length)\n return swal('An error occurred!', `Unable to delete ${failed.length} of the user's upload${failed.length === 1 ? '' : 's'}.`, 'error')\n else\n return swal('An error occurred!', response.data.description, 'error')\n }\n\n swal('Success!', `${page.cache.users[id].username} has been deleted.`, 'success')\n\n // Reload users list\n // eslint-disable-next-line compat/compat\n page.getUsers(Object.assign({\n autoPage: true\n }, page.views.users))\n }).catch(page.onAxiosError)\n })\n}\n\n// Roughly based on https://github.com/mayuska/pagination/blob/master/index.js\npage.paginate = (totalItems, itemsPerPage, currentPage) => {\n currentPage = currentPage + 1\n const step = 3\n const numPages = Math.ceil(totalItems / itemsPerPage)\n\n let template = ''\n const elementsToShow = step * 2\n const add = {\n pageNum (start, end) {\n for (let i = start; i <= end; ++i)\n template += `${keys[i].toUpperCase()} | \n\n |
---|