/* global swal, axios, ClipboardJS, LazyLoad */ // keys for localStorage const lsKeys = { token: 'token', viewType: { uploads: 'viewTypeUploads', uploadsAll: 'viewTypeUploadsAll' }, selected: { uploads: 'selectedUploads', uploadsAll: 'selectedUploadsAll', users: 'selectedUsers' } } const page = { // #page dom: null, // user token token: localStorage[lsKeys.token], // from api/tokens/verify username: null, permissions: null, currentView: null, views: { // config of uploads view uploads: { type: localStorage[lsKeys.viewType.uploads], album: null, // album's id pageNum: null // page num }, // config of uploads view (all) uploadsAll: { type: localStorage[lsKeys.viewType.uploadsAll], filters: null, // uploads' filters pageNum: null, // page num all: true }, // config of users view users: { pageNum: null } }, // id of selected items (shared across pages and will be synced with localStorage) selected: { uploads: [], uploadsAll: [], users: [] }, checkboxes: { uploads: [], uploadsAll: [], users: [] }, lastSelected: { upload: null, uploadsAll: null, user: null }, // select album dom for dialogs/modals selectAlbumContainer: null, // cache for dialogs/modals cache: { uploads: {}, albums: {}, users: {} }, clipboardJS: null, lazyLoad: null, imageExtensions: ['.webp', '.jpg', '.jpeg', '.bmp', '.gif', '.png'], fadingIn: null } page.preparePage = function () { if (!page.token) { window.location = 'auth' return } page.verifyToken(page.token, true) } page.verifyToken = function (token, reloadOnError) { axios.post('api/tokens/verify', { token }).then(function (response) { if (response.data.success === false) return swal({ title: 'An error occurred!', text: response.data.description, icon: 'error' }).then(function () { if (!reloadOnError) return localStorage.removeItem(lsKeys.token) location.location = 'auth' }) axios.defaults.headers.common.token = token localStorage[lsKeys.token] = token page.token = token page.username = response.data.username page.permissions = response.data.permissions page.prepareDashboard() }).catch(function (error) { console.log(error) return swal('An error occurred!', 'There was an error with the request, please check the console for more information.', 'error') }) } page.prepareDashboard = function () { page.dom = document.getElementById('page') page.dom.addEventListener('click', page.domClick, true) // document.getElementById('auth').style.display = 'none' document.getElementById('dashboard').style.display = 'block' if (page.permissions.moderator) { const itemManageUploads = document.getElementById('itemManageUploads') itemManageUploads.removeAttribute('disabled') itemManageUploads.addEventListener('click', function () { page.setActiveMenu(this) page.getUploads({ all: true }) }) } if (page.permissions.admin) { const itemServerStats = document.getElementById('itemServerStats') itemServerStats.removeAttribute('disabled') itemServerStats.addEventListener('click', function () { page.setActiveMenu(this) page.getServerStats() }) const itemManageUsers = document.getElementById('itemManageUsers') itemManageUsers.removeAttribute('disabled') itemManageUsers.addEventListener('click', function () { page.setActiveMenu(this) page.getUsers() }) } document.getElementById('itemUploads').addEventListener('click', function () { page.setActiveMenu(this) page.getUploads({ all: false }) }) document.getElementById('itemDeleteByNames').addEventListener('click', function () { page.setActiveMenu(this) page.deleteByNames() }) document.getElementById('itemManageGallery').addEventListener('click', function () { page.setActiveMenu(this) page.getAlbums() }) document.getElementById('itemFileLength').addEventListener('click', function () { page.setActiveMenu(this) page.changeFileLength() }) document.getElementById('itemTokens').addEventListener('click', function () { page.setActiveMenu(this) page.changeToken() }) document.getElementById('itemPassword').addEventListener('click', function () { page.setActiveMenu(this) page.changePassword() }) const logoutBtn = document.getElementById('itemLogout') logoutBtn.addEventListener('click', function () { page.logout() }) logoutBtn.innerHTML = `Logout ( ${page.username} )` page.getAlbumsSidebar() if (typeof page.prepareShareX === 'function') page.prepareShareX() } page.logout = function () { localStorage.removeItem(lsKeys.token) location.reload('.') } page.getItemID = function (element) { // This expects the item's parent to have the item's ID let parent = element.parentNode // If the element is part of a set of controls, use the container's parent instead if (element.parentNode.classList.contains('controls')) parent = parent.parentNode return parseInt(parent.dataset.id) } page.domClick = function (event) { // We are processing clicks this way to avoid using "onclick" attribute // Apparently we will need to use "unsafe-inline" for "script-src" directive // of Content Security Policy (CSP), if we want to use "onclick" attribute // Though I think that only applies to some browsers (?) // Either way, I personally would rather not // Of course it wouldn't have mattered if we didn't use CSP to begin with let element = event.target if (!element) return // If the clicked element is an icon, delegate event to its A parent; hacky if (element.tagName === 'I' && element.parentNode.tagName === 'SPAN') element = element.parentNode if (element.tagName === 'SPAN' && element.parentNode.tagName === 'A') element = element.parentNode // Skip elements that have no action data if (!element.dataset || !element.dataset.action) return // Skip disabled elements if (element.hasAttribute('disabled')) return event.stopPropagation() // maybe necessary const id = page.getItemID(element) const action = element.dataset.action switch (action) { case 'view-list': return page.setUploadsView('list', element) case 'view-thumbs': return page.setUploadsView('thumbs', element) case 'clear-selection': return page.clearSelection() case 'add-selected-files-to-album': return page.addSelectedFilesToAlbum() case 'bulk-delete': return page.deleteSelectedFiles() case 'select': return page.select(element, event) case 'add-to-album': return page.addSingleFileToAlbum(id) case 'delete-file': return page.deleteFile(id) case 'select-all': return page.selectAll(element) case 'display-thumbnail': return page.displayThumbnail(id) case 'delete-file-by-names': return page.deleteFileByNames() case 'submit-album': return page.submitAlbum(element) case 'edit-album': return page.editAlbum(id) case 'delete-album': return page.deleteAlbum(id) case 'get-new-token': return page.getNewToken(element) case 'edit-user': return page.editUser(id) case 'disable-user': return page.disableUser(id) case 'filters-help': return page.filtersHelp(element) case 'filter-uploads': return page.filterUploads(element) case 'view-user-uploads': return page.viewUserUploads(id) case 'page-prev': case 'page-next': case 'page-goto': case 'jump-to-page': return page.switchPage(action, element) } } page.isLoading = function (element, state) { if (!element) return if (state) return element.classList.add('is-loading') element.classList.remove('is-loading') } page.fadeIn = function (content) { if (page.fadingIn) { clearTimeout(page.fadingIn) page.dom.classList.remove('fade-in') } page.dom.classList.add('fade-in') page.fadingIn = setTimeout(function () { page.dom.classList.remove('fade-in') }, 500) } page.switchPage = function (action, element) { const views = {} let func = null if (page.currentView === 'users') { func = page.getUsers } else { func = page.getUploads views.album = page.views[page.currentView].album views.all = page.views[page.currentView].all views.filters = page.views[page.currentView].filters } switch (action) { case 'page-prev': views.pageNum = page.views[page.currentView].pageNum - 1 if (views.pageNum < 0) return swal('An error occurred!', 'This is already the first page.', 'error') return func(views, element) case 'page-next': views.pageNum = page.views[page.currentView].pageNum + 1 return func(views, element) case 'page-goto': views.pageNum = parseInt(element.dataset.goto) return func(views, element) case 'jump-to-page': const jumpToPage = parseInt(document.getElementById('jumpToPage').value) views.pageNum = isNaN(jumpToPage) ? 0 : (jumpToPage - 1) if (views.pageNum < 0) views.pageNum = 0 return func(views, element) } } page.getUploads = function ({ pageNum, album, all, filters } = {}, element) { if (element) page.isLoading(element, true) if ((all || filters) && !page.permissions.moderator) return swal('An error occurred!', 'You can not do this!', 'error') if (typeof pageNum !== 'number' || pageNum < 0) pageNum = 0 let url = `api/uploads/${pageNum}` if (typeof album === 'string') url = `api/album/${album}/${pageNum}` const headers = {} if (all) headers.all = '1' if (filters) headers.filters = filters axios.get(url, { headers }).then(function (response) { if (response.data.success === false) if (response.data.description === 'No token provided') { return page.verifyToken(page.token) } else { if (element) page.isLoading(element, false) return swal('An error occurred!', response.data.description, 'error') } const files = response.data.files if (pageNum && (files.length === 0)) { if (element) page.isLoading(element, false) return swal('An error occurred!', `There are no more uploads to populate page ${pageNum + 1}.`, 'error') } page.currentView = all ? 'uploadsAll' : 'uploads' page.cache.uploads = {} const albums = response.data.albums const users = response.data.users const basedomain = response.data.basedomain const pagination = page.paginate(response.data.count, 25, pageNum) let filter = '
' if (all) filter = `
` const extraControls = `
${filter}
` const controls = `
${all ? '' : ` `} Bulk delete
` // Set to true to tick "all files" checkbox in list view let allSelected = true for (let i = 0; i < files.length; i++) { // Build full URLs files[i].file = `${basedomain}/${files[i].name}` if (files[i].thumb) files[i].thumb = `${basedomain}/${files[i].thumb}` // Cache bare minimum data for thumbnails viewer page.cache.uploads[files[i].id] = { name: files[i].name, thumb: files[i].thumb, original: files[i].file } // Prettify files[i].prettyBytes = page.getPrettyBytes(parseInt(files[i].size)) files[i].prettyDate = page.getPrettyDate(new Date(files[i].timestamp * 1000)) // Update selected status files[i].selected = page.selected[page.currentView].includes(files[i].id) if (allSelected && !files[i].selected) allSelected = false // Appendix (display album or user) if (all) files[i].appendix = files[i].userid ? users[files[i].userid] : '' else files[i].appendix = files[i].albumid ? albums[files[i].albumid] : '' } if (page.views[page.currentView].type === 'thumbs') { page.dom.innerHTML = ` ${pagination} ${extraControls} ${controls}

${pagination} ` page.fadeIn() const table = document.getElementById('table') for (let i = 0; i < files.length; i++) { const upload = files[i] const div = document.createElement('div') div.className = 'image-container column is-narrow' div.dataset.id = upload.id if (upload.thumb !== undefined) div.innerHTML = `${upload.name}` else div.innerHTML = `

${upload.extname || 'N/A'}

` div.innerHTML += `

${upload.name}

${upload.appendix ? `${upload.appendix} – ` : ''}${upload.prettyBytes}

` table.appendChild(div) page.checkboxes[page.currentView] = Array.from(table.querySelectorAll('.checkbox[data-action="select"]')) page.lazyLoad.update() } } else { page.dom.innerHTML = ` ${pagination} ${extraControls} ${controls}
${all ? '' : ''}
File ${all ? 'User' : 'Album'} SizeIPDate

${pagination} ` page.fadeIn() const table = document.getElementById('table') for (let i = 0; i < files.length; i++) { const upload = files[i] const tr = document.createElement('tr') tr.dataset.id = upload.id tr.innerHTML = ` ${upload.name} ${upload.appendix} ${upload.prettyBytes} ${all ? `${upload.ip || ''}` : ''} ${upload.prettyDate} ${all ? '' : ` `} ` table.appendChild(tr) page.checkboxes[page.currentView] = Array.from(table.querySelectorAll('.checkbox[data-action="select"]')) } } if (allSelected && files.length) { const selectAll = document.getElementById('selectAll') if (selectAll) selectAll.checked = true } if (page.currentView === 'uploads') page.views.uploads.album = album if (page.currentView === 'uploadsAll') page.views.uploadsAll.filters = filters page.views[page.currentView].pageNum = files.length ? pageNum : 0 }).catch(function (error) { if (element) page.isLoading(element, false) console.log(error) return swal('An error occurred!', 'There was an error with the request, please check the console for more information.', 'error') }) } page.setUploadsView = function (view, element) { localStorage[lsKeys.viewType[page.currentView]] = view page.views[page.currentView].type = view page.getUploads(page.views[page.currentView], element) } page.displayThumbnail = function (id) { const file = page.cache.uploads[id] if (!file.thumb) return const div = document.createElement('div') div.innerHTML = `
` if (file.original) { div.innerHTML += `
Load original
` div.querySelector('#swalOriginal').addEventListener('click', function () { const button = this const original = button.dataset.original button.classList.add('is-loading') const thumb = div.querySelector('#swalThumb') const exec = /.[\w]+(\?|$)/.exec(original) const isimage = exec && exec[0] && page.imageExtensions.includes(exec[0].toLowerCase()) if (isimage) { thumb.src = file.original thumb.onload = function () { button.style.display = 'none' document.body.querySelector('.swal-overlay .swal-modal:not(.is-expanded)').classList.add('is-expanded') } thumb.onerror = function () { button.className = 'button is-danger' button.innerHTML = 'Unable to load original' } } else { thumb.style.display = 'none' const video = document.createElement('video') video.id = 'swalVideo' video.controls = true video.src = file.original thumb.insertAdjacentElement('afterend', video) button.style.display = 'none' document.body.querySelector('.swal-overlay .swal-modal:not(.is-expanded)').classList.add('is-expanded') } }) } return swal({ content: div, buttons: false }).then(function () { const video = div.querySelector('#swalVideo') if (video) video.remove() // Restore modal size document.body.querySelector('.swal-overlay .swal-modal.is-expanded').classList.remove('is-expanded') }) } page.selectAll = function (element) { const checkboxes = page.checkboxes[page.currentView] const selected = page.selected[page.currentView] for (let i = 0; i < checkboxes.length; i++) { const id = page.getItemID(checkboxes[i]) if (isNaN(id)) continue if (checkboxes[i].checked !== element.checked) { checkboxes[i].checked = element.checked if (checkboxes[i].checked) selected.push(id) else selected.splice(selected.indexOf(id), 1) } } localStorage[lsKeys.selected[page.currentView]] = JSON.stringify(selected) page.selected[page.currentView] = selected element.title = element.checked ? 'Unselect all uploads' : 'Select all uploads' } page.selectInBetween = function (element, lastElement) { if (!element || !lastElement) return if (element === lastElement) return const checkboxes = page.checkboxes[page.currentView] if (!checkboxes || !checkboxes.length) return const thisIndex = checkboxes.indexOf(element) const lastIndex = checkboxes.indexOf(lastElement) const distance = thisIndex - lastIndex if (distance >= -1 && distance <= 1) return for (let i = 0; i < checkboxes.length; i++) if ((thisIndex > lastIndex && i > lastIndex && i < thisIndex) || (thisIndex < lastIndex && i > thisIndex && i < lastIndex)) { checkboxes[i].checked = true page.selected[page.currentView].push(page.getItemID(checkboxes[i])) } localStorage[lsKeys.selected[page.currentView]] = JSON.stringify(page.selected[page.currentView]) page.checkboxes[page.currentView] = checkboxes } page.select = function (element, event) { const lastSelected = page.lastSelected[page.currentView] if (event.shiftKey && lastSelected) page.selectInBetween(element, lastSelected) else page.lastSelected[page.currentView] = element const id = page.getItemID(element) if (isNaN(id)) return const selected = page.selected[page.currentView] if (!selected.includes(id) && element.checked) selected.push(id) else if (selected.includes(id) && !element.checked) selected.splice(selected.indexOf(id), 1) localStorage[lsKeys.selected[page.currentView]] = JSON.stringify(selected) page.selected[page.currentView] = selected } page.clearSelection = function () { const selected = page.selected[page.currentView] const type = page.currentView === 'users' ? 'users' : 'uploads' const count = selected.length if (!count) return swal('An error occurred!', `You have not selected any ${type}.`, 'error') const suffix = count === 1 ? type.substring(0, type.length - 1) : type return swal({ title: 'Are you sure?', text: `You are going to unselect ${count} ${suffix}.`, buttons: true }).then(function (proceed) { if (!proceed) return const checkboxes = page.checkboxes[page.currentView] for (let i = 0; i < checkboxes.length; i++) if (checkboxes[i].checked) checkboxes[i].checked = false localStorage[lsKeys.selected[page.currentView]] = '[]' page.selected[page.currentView] = [] const selectAll = document.getElementById('selectAll') if (selectAll) selectAll.checked = false return swal('Cleared selection!', `Unselected ${count} ${suffix}.`, 'success') }) } page.filtersHelp = function (element) { const content = document.createElement('div') content.style = 'text-align: left' content.innerHTML = ` This supports 3 filter keys, namely user (username), ip and name (file name). Each key can be specified more than once. Backlashes should be used if the usernames have spaces. There are also 2 additional flags, namely -user and -ip, which will match uploads by non-registered users and have no IPs respectively. How does it work? First, it will filter uploads matching ANY of the supplied user or ip keys. Then, it will refine the matches using the supplied name keys. Examples: Uploads from user with username "demo": user:demo Uploads from users with username either "John Doe" OR "demo": user:John\\ Doe user:demo Uploads from IP "127.0.0.1" AND which file names match "*.rar" OR "*.zip": ip:127.0.0.1 name:*.rar name:*.zip Uploads from user with username "test" OR from non-registered users: user:test -user `.trim().replace(/^ {6}/gm, '').replace(/\n/g, '
') swal({ content }) } page.filterUploads = function (element) { const filters = document.getElementById('filters').value page.getUploads({ all: true, filters }, element) } page.viewUserUploads = function (id) { const user = page.cache.users[id] if (!user) return page.setActiveMenu(document.getElementById('itemManageUploads')) page.getUploads({ all: true, filters: `user:${user.username.replace(/ /g, '\\ ')}` }) } page.deleteFile = function (id) { // TODO: Share function with bulk delete, just like 'add selected uploads to album' and 'add single file to album' swal({ title: 'Are you sure?', text: 'You won\'t be able to recover the file!', icon: 'warning', dangerMode: true, buttons: { cancel: true, confirm: { text: 'Yes, delete it!', closeModal: false } } }).then(function (proceed) { if (!proceed) return axios.post('api/upload/delete', { id }).then(function (response) { if (!response) return if (response.data.success === false) if (response.data.description === 'No token provided') { return page.verifyToken(page.token) } else { return swal('An error occurred!', response.data.description, 'error') } swal('Deleted!', 'The file has been deleted.', 'success') page.getUploads(page.views[page.currentView]) }).catch(function (error) { console.log(error) return swal('An error occurred!', 'There was an error with the request, please check the console for more information.', 'error') }) }) } page.deleteSelectedFiles = function () { const count = page.selected[page.currentView].length if (!count) return swal('An error occurred!', 'You have not selected any uploads.', 'error') const suffix = `upload${count === 1 ? '' : 's'}` let text = `You won't be able to recover ${count} ${suffix}!` if (page.currentView === 'uploadsAll') text += '\nBe aware, you may be nuking uploads by other users!' swal({ title: 'Are you sure?', text, icon: 'warning', dangerMode: true, buttons: { cancel: true, confirm: { text: `Yes, nuke the ${suffix}!`, closeModal: false } } }).then(function (proceed) { if (!proceed) return axios.post('api/upload/bulkdelete', { field: 'id', values: page.selected[page.currentView] }).then(function (bulkdelete) { if (!bulkdelete) return if (bulkdelete.data.success === false) if (bulkdelete.data.description === 'No token provided') { return page.verifyToken(page.token) } else { return swal('An error occurred!', bulkdelete.data.description, 'error') } let deleted = count if (bulkdelete.data.failed && bulkdelete.data.failed.length) { deleted -= bulkdelete.data.failed.length page.selected[page.currentView] = page.selected[page.currentView].filter(function (id) { return bulkdelete.data.failed.includes(id) }) } else { page.selected[page.currentView] = [] } localStorage[lsKeys.selected[page.currentView]] = JSON.stringify(page.selected[page.currentView]) swal('Deleted!', `${deleted} file${deleted === 1 ? ' has' : 's have'} been deleted.`, 'success') return page.getUploads(page.views[page.currentView]) }).catch(function (error) { console.log(error) swal('An error occurred!', 'There was an error with the request, please check the console for more information.', 'error') }) }) } page.deleteByNames = function () { let appendix = '' if (page.permissions.moderator) appendix = '
As a staff, you can use this feature to delete uploads by other users.' page.dom.innerHTML = `

Delete by names

Separate each entry with a new line.${appendix}

Bulk delete
` page.fadeIn() } page.deleteFileByNames = function () { const names = document.getElementById('names').value .split(/\r?\n/) .filter(function (n) { return n.trim().length }) const count = names.length if (!count) return swal('An error occurred!', 'You have not entered any file names.', 'error') const suffix = `file${count === 1 ? '' : 's'}` swal({ title: 'Are you sure?', text: `You won't be able to recover ${count} ${suffix}!`, icon: 'warning', dangerMode: true, buttons: { cancel: true, confirm: { text: `Yes, nuke the ${suffix}!`, closeModal: false } } }).then(function (proceed) { if (!proceed) return axios.post('api/upload/bulkdelete', { field: 'name', values: names }).then(function (bulkdelete) { if (!bulkdelete) return if (bulkdelete.data.success === false) if (bulkdelete.data.description === 'No token provided') { return page.verifyToken(page.token) } else { return swal('An error occurred!', bulkdelete.data.description, 'error') } let deleted = count if (bulkdelete.data.failed && bulkdelete.data.failed.length) deleted -= bulkdelete.data.failed.length document.getElementById('names').value = bulkdelete.data.failed.join('\n') swal('Deleted!', `${deleted} file${deleted === 1 ? ' has' : 's have'} been deleted.`, 'success') }).catch(function (error) { console.log(error) swal('An error occurred!', 'There was an error with the request, please check the console for more information.', 'error') }) }) } page.addSelectedFilesToAlbum = function () { if (page.currentView !== 'uploads') return const count = page.selected[page.currentView].length if (!count) return swal('An error occurred!', 'You have not selected any uploads.', 'error') page.addFilesToAlbum(page.selected[page.currentView], function (failed) { if (!failed) return if (failed.length) page.selected[page.currentView] = page.selected[page.currentView].filter(function (id) { return failed.includes(id) }) else page.selected[page.currentView] = [] localStorage[lsKeys.selected[page.currentView]] = JSON.stringify(page.selected[page.currentView]) page.getUploads(page.views[page.currentView]) }) } page.addSingleFileToAlbum = function (id) { page.addFilesToAlbum([id], function (failed) { if (!failed) return page.getUploads(page.views[page.currentView]) }) } page.addFilesToAlbum = function (ids, callback) { const count = ids.length const content = document.createElement('div') content.innerHTML = `

You are about to add ${count} file${count === 1 ? '' : 's'} to an album.

If a file is already in an album, it will be moved.

` swal({ icon: 'warning', content, buttons: { cancel: true, confirm: { text: 'OK', closeModal: false } } }).then(function (choose) { if (!choose) return const albumid = parseInt(document.getElementById('swalAlbum').value) if (isNaN(albumid)) return swal('An error occurred!', 'You did not choose an album.', 'error') axios.post('api/albums/addfiles', { ids, albumid }).then(function (add) { if (!add) return if (add.data.success === false) { if (add.data.description === 'No token provided') page.verifyToken(page.token) else swal('An error occurred!', add.data.description, 'error') return } let added = ids.length if (add.data.failed && add.data.failed.length) added -= add.data.failed.length const suffix = `file${ids.length === 1 ? '' : 's'}` if (!added) return swal('An error occurred!', `Could not add the ${suffix} to the album.`, 'error') swal('Woohoo!', `Successfully ${albumid < 0 ? 'removed' : 'added'} ${added} ${suffix} ${albumid < 0 ? 'from' : 'to'} the album.`, 'success') return callback(add.data.failed) }).catch(function (error) { console.log(error) return swal('An error occurred!', 'There was an error with the request, please check the console for more information.', 'error') }) }).catch(function (error) { console.log(error) return swal('An error occurred!', 'There was an error with the request, please check the console for more information.', 'error') }) // Get albums list then update content of swal axios.get('api/albums').then(function (list) { if (list.data.success === false) { if (list.data.description === 'No token provided') page.verifyToken(page.token) else swal('An error occurred!', list.data.description, 'error') return } const select = document.getElementById('swalAlbum') // If the prompt was replaced, the container would be missing if (!select) return select.innerHTML += list.data.albums .map(function (album) { return `` }) .join('\n') select.getElementsByTagName('option')[1].innerHTML = 'Choose an album' select.removeAttribute('disabled') }).catch(function (error) { console.log(error) return swal('An error occurred!', 'There was an error with the request, please check the console for more information.', 'error') }) } page.getAlbums = function () { axios.get('api/albums').then(function (response) { if (!response) return if (response.data.success === false) if (response.data.description === 'No token provided') { return page.verifyToken(page.token) } else { return swal('An error occurred!', response.data.description, 'error') } page.cache.albums = {} page.dom.innerHTML = `

Create new album

Create

List of albums

ID Name Files Created at Public link
` page.fadeIn() const homeDomain = response.data.homeDomain const table = document.getElementById('table') for (let i = 0; i < response.data.albums.length; i++) { const album = response.data.albums[i] const albumUrl = `${homeDomain}/a/${album.identifier}` // Prettify album.prettyDate = page.getPrettyDate(new Date(album.timestamp * 1000)) page.cache.albums[album.id] = { name: album.name, download: album.download, public: album.public, description: album.description } const tr = document.createElement('tr') tr.innerHTML = ` ${album.id} ${album.name} ${album.files} ${album.prettyDate} ${albumUrl} ` table.appendChild(tr) } }).catch(function (error) { console.log(error) return swal('An error occurred!', 'There was an error with the request, please check the console for more information.', 'error') }) } page.editAlbum = function (id) { const album = page.cache.albums[id] if (!album) return const div = document.createElement('div') div.innerHTML = `
` swal({ title: 'Edit album', icon: 'info', content: div, buttons: { cancel: true, confirm: { closeModal: false } } }).then(function (value) { if (!value) return axios.post('api/albums/edit', { id, name: document.getElementById('swalName').value, description: document.getElementById('swalDescription').value, download: document.getElementById('swalDownload').checked, public: document.getElementById('swalPublic').checked, requestLink: document.getElementById('swalRequestLink').checked }).then(function (response) { if (!response) return if (response.data.success === false) if (response.data.description === 'No token provided') { return page.verifyToken(page.token) } else { return swal('An error occurred!', response.data.description, 'error') } if (response.data.identifier) swal('Success!', `Your album's new identifier is: ${response.data.identifier}.`, 'success') else if (response.data.name !== album.name) swal('Success!', `Your album was renamed to: ${response.data.name}.`, 'success') else swal('Success!', 'Your album was edited!', 'success') page.getAlbumsSidebar() page.getAlbums() }).catch(function (error) { console.log(error) return swal('An error occurred!', 'There was an error with the request, please check the console for more information.', 'error') }) }) } page.deleteAlbum = function (id) { swal({ title: 'Are you sure?', text: 'This won\'t delete your uploads, only the album!', icon: 'warning', dangerMode: true, buttons: { cancel: true, confirm: { text: 'Yes, delete it!', closeModal: false }, purge: { text: 'Umm, delete the uploads too please?', value: 'purge', className: 'swal-button--danger', closeModal: false } } }).then(function (proceed) { if (!proceed) return axios.post('api/albums/delete', { id, purge: proceed === 'purge' }).then(function (response) { if (response.data.success === false) if (response.data.description === 'No token provided') { return page.verifyToken(page.token) } else { return swal('An error occurred!', response.data.description, 'error') } swal('Deleted!', 'Your album has been deleted.', 'success') page.getAlbumsSidebar() page.getAlbums() }).catch(function (error) { console.log(error) return swal('An error occurred!', 'There was an error with the request, please check the console for more information.', 'error') }) }) } page.submitAlbum = function (element) { page.isLoading(element, true) axios.post('api/albums', { name: document.getElementById('albumName').value, description: document.getElementById('albumDescription').value }).then(function (response) { if (!response) return page.isLoading(element, false) if (response.data.success === false) if (response.data.description === 'No token provided') { return page.verifyToken(page.token) } else { return swal('An error occurred!', response.data.description, 'error') } swal('Woohoo!', 'Album was created successfully', 'success') page.getAlbumsSidebar() page.getAlbums() }).catch(function (error) { console.log(error) page.isLoading(element, false) return swal('An error occurred!', 'There was an error with the request, please check the console for more information.', 'error') }) } page.getAlbumsSidebar = function () { axios.get('api/albums/sidebar').then(function (response) { if (!response) return if (response.data.success === false) if (response.data.description === 'No token provided') { return page.verifyToken(page.token) } else { return swal('An error occurred!', response.data.description, 'error') } const albumsContainer = document.getElementById('albumsContainer') albumsContainer.innerHTML = '' if (response.data.albums === undefined) return for (let i = 0; i < response.data.albums.length; i++) { const album = response.data.albums[i] const li = document.createElement('li') const a = document.createElement('a') a.id = album.id a.innerHTML = album.name a.addEventListener('click', function () { page.getAlbum(this) }) li.appendChild(a) albumsContainer.appendChild(li) } }).catch(function (error) { console.log(error) return swal('An error occurred!', 'There was an error with the request, please check the console for more information.', 'error') }) } page.getAlbum = function (album) { page.setActiveMenu(album) page.getUploads({ album: album.id }) } page.changeFileLength = function () { axios.get('api/filelength/config').then(function (response) { if (response.data.success === false) if (response.data.description === 'No token provided') { return page.verifyToken(page.token) } else { return swal('An error occurred!', response.data.description, 'error') } // Shorter vars const { max, min } = response.data.config const [ chg, def ] = [ response.data.config.userChangeable, response.data.config.default ] const len = response.data.fileLength page.dom.innerHTML = `

File name length

Default file name length is ${def} characters. ${(chg ? `Range allowed for user is ${min} to ${max} characters.` : 'Changing file name length is disabled at the moment.')}

` page.fadeIn() document.getElementById('setFileLength').addEventListener('click', function () { page.setFileLength(document.getElementById('fileLength').value, this) }) }).catch(function (error) { console.log(error) return swal('An error occurred!', 'There was an error with the request, please check the console for more information.', 'error') }) } page.setFileLength = function (fileLength, element) { page.isLoading(element, true) axios.post('api/filelength/change', { fileLength }).then(function (response) { page.isLoading(element, false) if (response.data.success === false) if (response.data.description === 'No token provided') { return page.verifyToken(page.token) } else { return swal('An error occurred!', response.data.description, 'error') } swal({ title: 'Woohoo!', text: 'Your file length was successfully changed.', icon: 'success' }).then(function () { page.changeFileLength() }) }).catch(function (error) { console.log(error) page.isLoading(element, false) return swal('An error occurred!', 'There was an error with the request, please check the console for more information.', 'error') }) } page.changeToken = function () { axios.get('api/tokens').then(function (response) { if (response.data.success === false) if (response.data.description === 'No token provided') { return page.verifyToken(page.token) } else { return swal('An error occurred!', response.data.description, 'error') } page.dom.innerHTML = `

Manage your token

` page.fadeIn() }).catch(function (error) { console.log(error) return swal('An error occurred!', 'There was an error with the request, please check the console for more information.', 'error') }) } page.getNewToken = function (element) { page.isLoading(element, true) axios.post('api/tokens/change').then(function (response) { page.isLoading(element, false) if (response.data.success === false) if (response.data.description === 'No token provided') { return page.verifyToken(page.token) } else { return swal('An error occurred!', response.data.description, 'error') } swal({ title: 'Woohoo!', text: 'Your token was successfully changed.', icon: 'success' }).then(function () { axios.defaults.headers.common.token = response.data.token localStorage[lsKeys.token] = response.data.token page.token = response.data.token page.changeToken() }) }).catch(function (error) { console.log(error) page.isLoading(element, false) return swal('An error occurred!', 'There was an error with the request, please check the console for more information.', 'error') }) } page.changePassword = function () { page.dom.innerHTML = `

Change your password

` page.fadeIn() document.getElementById('sendChangePassword').addEventListener('click', function () { if (document.getElementById('password').value === document.getElementById('passwordConfirm').value) page.sendNewPassword(document.getElementById('password').value, this) else swal({ title: 'Password mismatch!', text: 'Your passwords do not match, please try again.', icon: 'error' }) }) } page.sendNewPassword = function (pass, element) { page.isLoading(element, true) axios.post('api/password/change', { password: pass }).then(function (response) { page.isLoading(element, false) if (response.data.success === false) if (response.data.description === 'No token provided') { return page.verifyToken(page.token) } else { return swal('An error occurred!', response.data.description, 'error') } swal({ title: 'Woohoo!', text: 'Your password was successfully changed.', icon: 'success' }).then(function () { page.changePassword() }) }).catch(function (error) { console.log(error) page.isLoading(element, false) return swal('An error occurred!', 'There was an error with the request, please check the console for more information.', 'error') }) } page.setActiveMenu = function (activeItem) { const menu = document.getElementById('menu') const items = menu.getElementsByTagName('a') for (let i = 0; i < items.length; i++) items[i].classList.remove('is-active') activeItem.classList.add('is-active') } page.getUsers = function ({ pageNum } = {}, element) { if (element) page.isLoading(element, true) if (pageNum === undefined) pageNum = 0 if (!page.permissions.admin) return swal('An error occurred!', 'You can not do this!', 'error') const url = `api/users/${pageNum}` axios.get(url).then(function (response) { if (response.data.success === false) if (response.data.description === 'No token provided') { return page.verifyToken(page.token) } else { return swal('An error occurred!', response.data.description, 'error') } if (pageNum && (response.data.users.length === 0)) { // Only remove loading class here, since beyond this the entire page will be replaced anyways if (element) page.isLoading(element, false) return swal('An error occurred!', `There are no more users to populate page ${pageNum + 1}.`, 'error') } page.currentView = 'users' page.cache.users = {} const pagination = page.paginate(response.data.count, 25, pageNum) const extraControls = ` ` const controls = ` ` let allSelected = true page.dom.innerHTML = ` ${pagination} ${extraControls} ${controls}
ID Username Uploads Usage File length Group

${pagination} ` page.fadeIn() const table = document.getElementById('table') for (let i = 0; i < response.data.users.length; i++) { const user = response.data.users[i] const selected = page.selected.users.includes(user.id) if (!selected && allSelected) allSelected = false let displayGroup = null for (const group of Object.keys(user.groups)) { if (!user.groups[group]) break displayGroup = group } // Server-side explicitly expects either of these two values to consider a user as disabled const enabled = user.enabled !== false && user.enabled !== 0 page.cache.users[user.id] = { username: user.username, groups: user.groups, enabled, displayGroup } const tr = document.createElement('tr') tr.dataset.id = user.id tr.innerHTML = ` ${user.id} ${user.username} ${user.uploadsCount} ${page.getPrettyBytes(user.diskUsage)} ${user.fileLength || 'default'} ${displayGroup} ` table.appendChild(tr) // page.checkboxes.users = Array.from(table.getElementsByClassName('checkbox')) page.checkboxes.users = Array.from(table.querySelectorAll('.checkbox[data-action="select"]')) } if (allSelected && response.data.users.length) { const selectAll = document.getElementById('selectAll') if (selectAll) selectAll.checked = true } page.views.users.pageNum = response.data.users.length ? pageNum : 0 }).catch(function (error) { if (element) page.isLoading(element, false) console.log(error) return swal('An error occurred!', 'There was an error with the request, please check the console for more information.', 'error') }) } page.editUser = function (id) { const user = page.cache.users[id] if (!user) return const groupOptions = Object.keys(page.permissions).map(function (g, i, a) { const selected = g === user.displayGroup const disabled = !(a[i + 1] && page.permissions[a[i + 1]]) return `` }).join('\n') const div = document.createElement('div') div.innerHTML = `
` swal({ title: 'Edit user', icon: 'info', content: div, buttons: { cancel: true, confirm: { closeModal: false } } }).then(function (proceed) { if (!proceed) return axios.post('api/users/edit', { id, username: document.getElementById('swalUsername').value, group: document.getElementById('swalGroup').value, enabled: document.getElementById('swalEnabled').checked, resetPassword: document.getElementById('swalResetPassword').checked }).then(function (response) { if (!response) return if (response.data.success === false) if (response.data.description === 'No token provided') { return page.verifyToken(page.token) } else { return swal('An error occurred!', response.data.description, 'error') } if (response.data.password) { const div = document.createElement('div') div.innerHTML = `

${user.username}'s new password is:

${response.data.password}

` swal({ title: 'Success!', icon: 'success', content: div }) } else if (response.data.update && response.data.update.username !== user.username) { swal('Success!', `${user.username} was renamed into: ${response.data.update.name}.`, 'success') } else { swal('Success!', 'The user was edited!', 'success') } page.getUsers(page.views.users) }).catch(function (error) { console.log(error) return swal('An error occurred!', 'There was an error with the request, please check the console for more information.', 'error') }) }) } page.disableUser = function (id) { const user = page.cache.users[id] if (!user || !user.enabled) return const content = document.createElement('div') content.innerHTML = `You will be disabling a user with the username ${page.cache.users[id].username}!` swal({ title: 'Are you sure?', icon: 'warning', content, dangerMode: true, buttons: { cancel: true, confirm: { text: 'Yes, disable them!', closeModal: false } } }).then(function (proceed) { if (!proceed) return axios.post('api/users/disable', { id }).then(function (response) { if (!response) return if (response.data.success === false) if (response.data.description === 'No token provided') return page.verifyToken(page.token) else return swal('An error occurred!', response.data.description, 'error') swal('Success!', 'The user has been disabled.', 'success') page.getUsers(page.views.users) }).catch(function (error) { console.log(error) return swal('An error occurred!', 'There was an error with the request, please check the console for more information.', 'error') }) }) } page.paginate = function (totalItems, itemsPerPage, currentPage) { // Roughly based on https://github.com/mayuska/pagination/blob/master/index.js currentPage = currentPage + 1 const step = 3 const numPages = Math.ceil(totalItems / itemsPerPage) let template = '' const elementsToShow = step * 2 const add = { pageNum (start, end) { for (let i = start; i <= end; ++i) template += `
  • ${i}
  • ` }, startDots () { template += `
  • 1
  • ` }, endDots () { template += `
  • ${numPages}
  • ` } } if (elementsToShow + 1 >= numPages) { add.pageNum(1, numPages) } else if (currentPage < elementsToShow) { add.pageNum(1, elementsToShow) add.endDots() } else if (currentPage > numPages - elementsToShow + 1) { add.startDots() add.pageNum(numPages - elementsToShow + 1, numPages) } else { add.startDots() add.pageNum(currentPage - step + 1, currentPage + step - 1) add.endDots() } return ` ` } page.getServerStats = function (element) { if (!page.permissions.admin) return swal('An error occurred!', 'You can not do this!', 'error') page.dom.innerHTML = ` Please wait, this may take a while\u2026 ` page.fadeIn() const url = 'api/stats' axios.get(url).then(function (response) { if (response.data.success === false) if (response.data.description === 'No token provided') { return page.verifyToken(page.token) } else { return swal('An error occurred!', response.data.description, 'error') } let content = '' for (const key of Object.keys(response.data.stats)) { let rows = '' if (!response.data.stats[key]) rows += ` Generating, please try again later\u2026 ` else for (const valKey of Object.keys(response.data.stats[key])) { const _value = response.data.stats[key][valKey] let value = _value if (['albums', 'users', 'uploads'].includes(key)) value = _value.toLocaleString() if (['memoryUsage', 'size'].includes(valKey)) value = page.getPrettyBytes(_value) if (valKey === 'systemMemory') value = `${page.getPrettyBytes(_value.used)} / ${page.getPrettyBytes(_value.total)} (${Math.round(_value.used / _value.total * 100)}%)` rows += ` ${valKey.replace(/([A-Z])/g, ' $1').toUpperCase()} ${value} ` } content += `
    ${rows}
    ${key.toUpperCase()}
    ` } page.dom.innerHTML = `

    Statistics

    ${content} ` page.fadeIn() }) } page.getPrettyDate = function (date) { return date.getFullYear() + '-' + (date.getMonth() < 9 ? '0' : '') + // month's index starts from zero (date.getMonth() + 1) + '-' + (date.getDate() < 10 ? '0' : '') + date.getDate() + ' ' + (date.getHours() < 10 ? '0' : '') + date.getHours() + ':' + (date.getMinutes() < 10 ? '0' : '') + date.getMinutes() + ':' + (date.getSeconds() < 10 ? '0' : '') + date.getSeconds() } page.getPrettyBytes = function (num, si) { // MIT License // Copyright (c) Sindre Sorhus (sindresorhus.com) if (!Number.isFinite(num)) return num const neg = num < 0 ? '-' : '' const scale = si ? 1000 : 1024 if (neg) num = -num if (num < scale) return `${neg}${num} B` const exponent = Math.min(Math.floor(Math.log10(num) / 3), 8) // 8 is count of KMGTPEZY const numStr = Number((num / Math.pow(scale, exponent)).toPrecision(3)) const pre = (si ? 'kMGTPEZY' : 'KMGTPEZY').charAt(exponent - 1) + (si ? '' : 'i') return `${neg}${numStr} ${pre}B` } window.onload = function () { // Add 'no-touch' class to non-touch devices if (!('ontouchstart' in document.documentElement)) document.documentElement.classList.add('no-touch') const selectedKeys = ['uploads', 'uploadsAll', 'users'] for (const selectedKey of selectedKeys) { const ls = localStorage[lsKeys.selected[selectedKey]] if (ls) page.selected[selectedKey] = JSON.parse(ls) } page.preparePage() page.clipboardJS = new ClipboardJS('.clipboard-js') page.clipboardJS.on('success', function () { return swal('Copied!', 'The link has been copied to clipboard.', 'success') }) page.clipboardJS.on('error', function (event) { console.error(event) return swal('An error occurred!', 'There was an error when trying to copy the link to clipboard, please check the console for more information.', 'error') }) page.lazyLoad = new LazyLoad() }