mirror of
https://github.com/BobbyWibowo/lolisafe.git
synced 2025-01-18 17:21:33 +00:00
Updated
Added iamdustan/smoothscroll polyfill in dashboard pages. This will polyfill smooth scroll (when executed programmatically) for older browers. No-JS uploader's notice button when on private mode will now also say "Log in to upload", although auth page will still require JS. All front-end buttons will now use outlined version. I'm lovin' it. Auth page will now show a loading spinner if the user has a saved token. Afterwards, they will still be redirected to dashboard. Better error handlers in home, dashboard, and auth pages. Removed <hr> from uploads & users lists in dashboard. "Manage your token" menu will no longer try to make an API request prior to displaying its page. Reloading the page will already trigger token verification anyway. Updated public/images/fb_share.png. Updated README.md. A few other tweaks.
This commit is contained in:
parent
c3d4bc766e
commit
c3d61733af
127
README.md
127
README.md
@ -1,27 +1,57 @@
|
||||
# lolisafe, a small safe worth protecting
|
||||
|
||||
[![safe.fiery.me](https://i.fiery.me/zSYB.png)](https://safe.fiery.me)
|
||||
[![safe.fiery.me](https://i.fiery.me/2Eeb.png)](https://safe.fiery.me)
|
||||
|
||||
[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](https://raw.githubusercontent.com/WeebDev/lolisafe/master/LICENSE)
|
||||
|
||||
## `safe.fiery.me` branch
|
||||
## `safe.fiery.me`
|
||||
|
||||
[![JavaScript Style Guide](https://cdn.rawgit.com/standard/standard/master/badge.svg)](https://github.com/standard/standard)
|
||||
|
||||
This branch is the one being used at [https://safe.fiery.me](https://safe.fiery.me). If you are looking for the original, head to `master` branch, or even better to [WeebDev/lolisafe](https://github.com/WeebDev/lolisafe).
|
||||
This fork is the one being used at [https://safe.fiery.me](https://safe.fiery.me). If you are looking for the original, head to [WeebDev/lolisafe](https://github.com/WeebDev/lolisafe).
|
||||
|
||||
If you want to use an existing lolisafe database with this branch, make sure to run `node database/migration.js` at least once to create some new columns introduced in this branch.
|
||||
If you want to use an existing lolisafe database with this fork, run `node ./database/migration.js` at least once to create the new columns introduced in this branch (don't forget to make a backup).
|
||||
|
||||
Configuration file of lolisafe, `config.js`, is also NOT 100% compatible with this branch. There are some options that had been renamed and/or restructured. Please make sure your config matches the sample in `config.sample.js` before starting.
|
||||
Configuration file of lolisafe, `config.js`, is also NOT fully compatible with this fork. There are some options that had been renamed and/or restructured. Please make sure your config matches the sample in `config.sample.js` before starting.
|
||||
|
||||
## Missing thumbnails
|
||||
## Running in production mode
|
||||
|
||||
Thumbnails will not be automatically generated for existing files that have been uploaded prior to enabling thumbnails generation in the config file.
|
||||
1. Ensure you have at least Node v8.0.0 installed (v10.x is recommended).
|
||||
2. Clone this repo.
|
||||
3. Copy `config.sample.js` as `config.js`.
|
||||
4. Modify port, domain and privacy options if desired.
|
||||
5. Run `yarn install --production` to install all production dependencies (Yes, use [yarn](https://yarnpkg.com)).
|
||||
6. Run `yarn start` to start the service.
|
||||
|
||||
To generate thumbnails for those files, you can try running `node scripts/thumbs.js`.
|
||||
You can also start it with `yarn pm2` if you have [PM2](https://pm2.keymetrics.io/).
|
||||
|
||||
```
|
||||
$ node scripts/thumbs.js
|
||||
When running in production mode, the safe will use pre-built client-side CSS/JS files from `dist` directory, while the actual source codes are in `src` directory.
|
||||
|
||||
The pre-built files were processed with [postcss-preset-env](https://github.com/csstools/postcss-preset-env), [cssnano](https://github.com/cssnano/cssnano), [bublé](https://github.com/bublejs/buble), and [terser](https://github.com/terser/terser).
|
||||
|
||||
## Running in development mode
|
||||
|
||||
This fork has a separate development mode, with which client-side CSS/JS files in `src` directory will be automatically rebuilt using [Gulp](https://github.com/gulpjs/gulp#what-is-gulp) tasks.
|
||||
|
||||
1. Follow step 1 to 4 from the production instructions above.
|
||||
2. Run `yarn install` to install all dependencies (including development ones).
|
||||
3. Run `yarn develop` to start the service in development mode.
|
||||
|
||||
You can configure the Gulp tasks through `gulpfile.js` file.
|
||||
|
||||
During development, the rebuilt files will be saved in `dist-dev` directory instead of `dist` directory. The service will also automatically serve the files from `dist-dev` directory instead. This is to avoid your IDE's Git from unnecessarily rebuilding diff of the modified files.
|
||||
|
||||
Once you feel like your modifications are ready for production usage, you can then run `yarn build` to build production-ready files that will actually go to `dist` directory.
|
||||
|
||||
## Script for missing thumbnails
|
||||
|
||||
Thumbnails will not be automatically generated for existing files, that had been uploaded prior to enabling thumbnails in the config file.
|
||||
|
||||
To generate thumbnails for those files, you can use `yarn thumbs`.
|
||||
|
||||
```none
|
||||
$ yarn thumbs
|
||||
$ node ./scripts/thumbs.js
|
||||
|
||||
Generate thumbnails.
|
||||
|
||||
@ -34,7 +64,9 @@ verbose: 0 = only print missing thumbs (default), 1 = print all
|
||||
cfcache: 0 = do not clear cloudflare cache (default), 1 = clear cloudflare cache
|
||||
```
|
||||
|
||||
For example, if you only want to generate thumbnails for image files without overwriting existing ones (e.i. thumbnails of new files), you can run `node scripts/thumbs.js 1`.
|
||||
For example, if you only want to generate thumbnails for image files without overwriting existing ones, you can run `yarn thumbs 1`.
|
||||
|
||||
Or if you want to generate thumbnails for both image and video files, while also overwriting existsing ones, you can run `yarn thumbs 3 1`.
|
||||
|
||||
## ClamAV support
|
||||
|
||||
@ -42,77 +74,6 @@ This fork has an optional virus scanning support using [ClamAV](https://www.clam
|
||||
|
||||
It will scan new files right after they are uploaded. It will then print error messages to the uploaders (as in the virus names in ClamAV's databases) if the files are dirty.
|
||||
|
||||
On the down side, this will slow down uploads processing (as it has to wait for the scan results before responding the uploader's requests), however it's still highly recommended for public use.
|
||||
On the down side, this will slow down uploads processing (as it has to wait for the scan results before responding the uploader's requests), however it's still highly recommended for public usage.
|
||||
|
||||
To enable this, make sure you have ClamAV daemon running, then fill in the daemon's IP and port into your config file.
|
||||
|
||||
## Running
|
||||
|
||||
1. Ensure you have at least version 8.0.0 of node installed
|
||||
2. Clone the repo
|
||||
3. Rename `config.sample.js` to `config.js`
|
||||
4. Modify port, domain and privacy options if desired
|
||||
5. run `yarn install` to install all dependencies (yes, use [yarn](https://yarnpkg.com))
|
||||
6. run `pm2 start lolisafe.js` or `node lolisafe.js` to start the service
|
||||
|
||||
## Getting started
|
||||
|
||||
This service supports running both as public and private. The only difference is that one needs a token to upload and the other one doesn't. If you want it to be public so anyone can upload files either from the website or API, just set the option `private: false` in the `config.js` file. In case you want to run it privately, you should set `private: true`.
|
||||
|
||||
Upon running the service for the first time, it's gonna create a user account with the username `root` and password `root`. This is your admin account and you should change the password immediately. This account will let you manage all uploaded files and remove any if necessary.
|
||||
|
||||
The option `serveFilesWithNode` in the `config.js` dictates if you want lolisafe to serve the files or nginx/apache once they are uploaded. The main difference between the two is the ease of use and the chance of analytics in the future.
|
||||
If you set it to `true`, the uploaded files will be located after the host like:
|
||||
`https://lolisafe.moe/yourFile.jpg`
|
||||
|
||||
If you set it to `false`, you need to set nginx to directly serve whatever folder it is you are serving your
|
||||
downloads in. This also gives you the ability to serve them, for example, like this:
|
||||
`https://files.lolisafe.moe/yourFile.jpg`
|
||||
|
||||
Both cases require you to type the domain where the files will be served on the `domain` key below.
|
||||
Which one you use is ultimately up to you. Either way, I've provided a sample config files for nginx that you can use to set it up quickly and painlessly!
|
||||
|
||||
- [Normal Version](https://github.com/WeebDev/lolisafe/blob/master/nginx.sample.conf)
|
||||
- [SSL Version](https://github.com/WeebDev/lolisafe/blob/master/nginx-ssl.sample.conf)
|
||||
|
||||
If you set `enableUserAccounts: true`, people will be able to create accounts on the service to keep track of their uploaded files and create albums to upload stuff to, pretty much like imgur does, but only through the API. Every user account has a token that the user can use to upload stuff through the API. You can find this token on the section called `Change your token` on the administration dashboard, and if it gets leaked or compromised you can renew it by clicking the button titled `Request new token`.
|
||||
|
||||
## Cloudflare Support
|
||||
|
||||
If you are running lolisafe behind Cloudflare there is support to make the NGINX logs have the users IP instead of Cloudflares IP. You will need to compile NGINX from source with `--with-http_realip_module` as well as uncomment the following line in the NGINX config: `include /path/to/lolisafe/real-ip-from-cf;`
|
||||
|
||||
## Using lolisafe
|
||||
|
||||
Once the service starts you can start hitting the upload endpoint at `/api/upload` with any file. If you're using the frontend to do so then you are pretty much set, but if using the API to upload make sure the form name is set to `files[]` and the form type to `multipart/form-data`. If the service is running in private mode, dont forget to send a header of type `token: YOUR-CLIENT-TOKEN` to validate the request.
|
||||
|
||||
A sample of the returning json from the endpoint can be seen below:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "EW7C.png",
|
||||
"size": "71400",
|
||||
"url": "https://i.kanacchi.moe/EW7C.png"
|
||||
}
|
||||
```
|
||||
|
||||
To make it easier and better than any other service, you can download [our Chrome extension](https://chrome.google.com/webstore/detail/lolisafe-uploader/enkkmplljfjppcdaancckgilmgoiofnj). That will let you configure your hostname and tokens, so that you can simply `right click` -> `loli-safe` -> `send to safe` on any image/audio/video file on the web.
|
||||
|
||||
Because of how nodejs apps work, if you want it attached to a domain name you will need to make a reverse proxy for it.
|
||||
|
||||
Here is a tutorial [on how to do this with nginx](https://www.digitalocean.com/community/tutorials/how-to-set-up-a-node-js-application-for-production-on-ubuntu-16-04). Keep in mind that this is only a requirement if you want to access your lolisafe service by using a domain name (ex: `https://i.kanacchi.moe`), otherwise you can use the service just fine by accessing it from your server's IP.
|
||||
|
||||
## Sites using lolisafe
|
||||
|
||||
Refer to the [wiki](https://github.com/WeebDev/lolisafe/wiki/Sites-using-lolisafe)
|
||||
|
||||
## Author
|
||||
|
||||
[![Chat / Support](https://img.shields.io/badge/Chat%20%2F%20Support-discord-7289DA.svg?style=flat-square)](https://discord.gg/5g6vgwn)
|
||||
[![Support me](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Fshieldsio-patreon.herokuapp.com%2Fpitu&style=flat-square)](https://www.patreon.com/pitu)
|
||||
[![Support me](https://img.shields.io/badge/Support-Buy%20me%20a%20coffee-yellow.svg?style=flat-square)](https://www.buymeacoffee.com/kana)
|
||||
|
||||
**lolisafe** © [Pitu](https://github.com/Pitu), Released under the [MIT](https://github.com/WeebDev/lolisafe/blob/master/LICENSE) License.
|
||||
|
||||
Authored and maintained by Pitu.
|
||||
|
||||
> [lolisafe.moe](https://lolisafe.moe) · GitHub [@Pitu](https://github.com/Pitu)
|
||||
|
2
dist/js/auth.js
vendored
2
dist/js/auth.js
vendored
@ -1,2 +1,2 @@
|
||||
var lsKeys={token:"token"},page={token:localStorage[lsKeys.token],user:null,pass:null,do:function(e,r){var o=page.user.value.trim();if(!o)return swal("An error occurred!","You need to specify a username.","error");var t=page.pass.value.trim();if(!t)return swal("An error occurred!","You need to specify a password.","error");r.classList.add("is-loading"),axios.post("api/"+e,{username:o,password:t}).then((function(o){if(!1===o.data.success)return r.classList.remove("is-loading"),swal("Unable to "+e+"!",o.data.description,"error");localStorage.token=o.data.token,window.location="dashboard"})).catch((function(e){return console.error(e),r.classList.remove("is-loading"),swal("An error occurred!","There was an error with the request, please check the console for more information.","error")}))},verify:function(){page.token&&axios.post("api/tokens/verify",{token:page.token}).then((function(e){if(!1===e.data.success)return swal("An error occurred!",e.data.description,"error");window.location="dashboard"})).catch((function(e){console.error(e);var r=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+" "+e.response.statusText,r,"error")}))}};window.onload=function(){page.verify(),page.user=document.querySelector("#user"),page.pass=document.querySelector("#pass");var e=document.querySelector("#authForm");e.addEventListener("submit",(function(e){e.preventDefault()})),document.querySelector("#loginBtn").addEventListener("click",(function(r){e.checkValidity()&&page.do("login",r.currentTarget)})),document.querySelector("#registerBtn").addEventListener("click",(function(r){e.checkValidity()&&page.do("register",r.currentTarget)}))};
|
||||
var lsKeys={token:"token"},page={token:localStorage[lsKeys.token],user:null,pass:null,unhide:function(){document.querySelector("#loader").classList.add("is-hidden"),document.querySelector("#login").classList.remove("is-hidden")},onAxiosError:function(e){console.error(e);var r={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,o=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+" "+r,o,"error")},do:function(e,r){var o=page.user.value.trim();if(!o)return swal("An error occurred!","You need to specify a username.","error");var n=page.pass.value.trim();if(!n)return swal("An error occurred!","You need to specify a password.","error");r.classList.add("is-loading"),axios.post("api/"+e,{username:o,password:n}).then((function(o){if(!1===o.data.success)return r.classList.remove("is-loading"),swal("Unable to "+e+"!",o.data.description,"error");localStorage.token=o.data.token,window.location="dashboard"})).catch((function(e){r.classList.remove("is-loading"),page.onAxiosError(e)}))},verify:function(){axios.post("api/tokens/verify",{token:page.token}).then((function(e){if(!1===e.data.success)return page.unhide(),swal("An error occurred!",e.data.description,"error");window.location="dashboard"})).catch((function(e){page.unhide(),page.onAxiosError(e)}))}};window.onload=function(){page.user=document.querySelector("#user"),page.pass=document.querySelector("#pass");var e=document.querySelector("#authForm");e.addEventListener("submit",(function(e){e.preventDefault()})),document.querySelector("#loginBtn").addEventListener("click",(function(r){e.checkValidity()&&page.do("login",r.currentTarget)})),document.querySelector("#registerBtn").addEventListener("click",(function(r){e.checkValidity()&&page.do("register",r.currentTarget)})),page.token?page.verify():page.unhide()};
|
||||
//# sourceMappingURL=auth.js.map
|
||||
|
2
dist/js/auth.js.map
vendored
2
dist/js/auth.js.map
vendored
File diff suppressed because one or more lines are too long
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/home.js
vendored
2
dist/js/home.js
vendored
File diff suppressed because one or more lines are too long
2
dist/js/home.js.map
vendored
2
dist/js/home.js.map
vendored
File diff suppressed because one or more lines are too long
BIN
public/images/fb_share.png
Executable file → Normal file
BIN
public/images/fb_share.png
Executable file → Normal file
Binary file not shown.
Before Width: | Height: | Size: 93 KiB After Width: | Height: | Size: 176 KiB |
20
public/libs/smoothscroll/LICENSE
Normal file
20
public/libs/smoothscroll/LICENSE
Normal file
@ -0,0 +1,20 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 Dustan Kasten
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
1
public/libs/smoothscroll/smoothscroll.min.js
vendored
Normal file
1
public/libs/smoothscroll/smoothscroll.min.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
!function(){"use strict";function o(){var o=window,t=document;if(!("scrollBehavior"in t.documentElement.style&&!0!==o.__forceSmoothScrollPolyfill__)){var l,e=o.HTMLElement||o.Element,r=468,i={scroll:o.scroll||o.scrollTo,scrollBy:o.scrollBy,elementScroll:e.prototype.scroll||n,scrollIntoView:e.prototype.scrollIntoView},s=o.performance&&o.performance.now?o.performance.now.bind(o.performance):Date.now,c=(l=o.navigator.userAgent,new RegExp(["MSIE ","Trident/","Edge/"].join("|")).test(l)?1:0);o.scroll=o.scrollTo=function(){void 0!==arguments[0]&&(!0!==f(arguments[0])?h.call(o,t.body,void 0!==arguments[0].left?~~arguments[0].left:o.scrollX||o.pageXOffset,void 0!==arguments[0].top?~~arguments[0].top:o.scrollY||o.pageYOffset):i.scroll.call(o,void 0!==arguments[0].left?arguments[0].left:"object"!=typeof arguments[0]?arguments[0]:o.scrollX||o.pageXOffset,void 0!==arguments[0].top?arguments[0].top:void 0!==arguments[1]?arguments[1]:o.scrollY||o.pageYOffset))},o.scrollBy=function(){void 0!==arguments[0]&&(f(arguments[0])?i.scrollBy.call(o,void 0!==arguments[0].left?arguments[0].left:"object"!=typeof arguments[0]?arguments[0]:0,void 0!==arguments[0].top?arguments[0].top:void 0!==arguments[1]?arguments[1]:0):h.call(o,t.body,~~arguments[0].left+(o.scrollX||o.pageXOffset),~~arguments[0].top+(o.scrollY||o.pageYOffset)))},e.prototype.scroll=e.prototype.scrollTo=function(){if(void 0!==arguments[0])if(!0!==f(arguments[0])){var o=arguments[0].left,t=arguments[0].top;h.call(this,this,void 0===o?this.scrollLeft:~~o,void 0===t?this.scrollTop:~~t)}else{if("number"==typeof arguments[0]&&void 0===arguments[1])throw new SyntaxError("Value could not be converted");i.elementScroll.call(this,void 0!==arguments[0].left?~~arguments[0].left:"object"!=typeof arguments[0]?~~arguments[0]:this.scrollLeft,void 0!==arguments[0].top?~~arguments[0].top:void 0!==arguments[1]?~~arguments[1]:this.scrollTop)}},e.prototype.scrollBy=function(){void 0!==arguments[0]&&(!0!==f(arguments[0])?this.scroll({left:~~arguments[0].left+this.scrollLeft,top:~~arguments[0].top+this.scrollTop,behavior:arguments[0].behavior}):i.elementScroll.call(this,void 0!==arguments[0].left?~~arguments[0].left+this.scrollLeft:~~arguments[0]+this.scrollLeft,void 0!==arguments[0].top?~~arguments[0].top+this.scrollTop:~~arguments[1]+this.scrollTop))},e.prototype.scrollIntoView=function(){if(!0!==f(arguments[0])){var l=function(o){for(;o!==t.body&&!1===(e=p(l=o,"Y")&&a(l,"Y"),r=p(l,"X")&&a(l,"X"),e||r);)o=o.parentNode||o.host;var l,e,r;return o}(this),e=l.getBoundingClientRect(),r=this.getBoundingClientRect();l!==t.body?(h.call(this,l,l.scrollLeft+r.left-e.left,l.scrollTop+r.top-e.top),"fixed"!==o.getComputedStyle(l).position&&o.scrollBy({left:e.left,top:e.top,behavior:"smooth"})):o.scrollBy({left:r.left,top:r.top,behavior:"smooth"})}else i.scrollIntoView.call(this,void 0===arguments[0]||arguments[0])}}function n(o,t){this.scrollLeft=o,this.scrollTop=t}function f(o){if(null===o||"object"!=typeof o||void 0===o.behavior||"auto"===o.behavior||"instant"===o.behavior)return!0;if("object"==typeof o&&"smooth"===o.behavior)return!1;throw new TypeError("behavior member of ScrollOptions "+o.behavior+" is not a valid value for enumeration ScrollBehavior.")}function p(o,t){return"Y"===t?o.clientHeight+c<o.scrollHeight:"X"===t?o.clientWidth+c<o.scrollWidth:void 0}function a(t,l){var e=o.getComputedStyle(t,null)["overflow"+l];return"auto"===e||"scroll"===e}function d(t){var l,e,i,c,n=(s()-t.startTime)/r;c=n=n>1?1:n,l=.5*(1-Math.cos(Math.PI*c)),e=t.startX+(t.x-t.startX)*l,i=t.startY+(t.y-t.startY)*l,t.method.call(t.scrollable,e,i),e===t.x&&i===t.y||o.requestAnimationFrame(d.bind(o,t))}function h(l,e,r){var c,f,p,a,h=s();l===t.body?(c=o,f=o.scrollX||o.pageXOffset,p=o.scrollY||o.pageYOffset,a=i.scroll):(c=l,f=l.scrollLeft,p=l.scrollTop,a=n),d({scrollable:c,method:a,startTime:h,startX:f,startY:p,x:e,y:r})}}"object"==typeof exports&&"undefined"!=typeof module?module.exports={polyfill:o}:o()}();
|
@ -13,6 +13,36 @@ const page = {
|
||||
pass: null
|
||||
}
|
||||
|
||||
page.unhide = () => {
|
||||
document.querySelector('#loader').classList.add('is-hidden')
|
||||
document.querySelector('#login').classList.remove('is-hidden')
|
||||
}
|
||||
|
||||
// Handler for Axios errors
|
||||
page.onAxiosError = error => {
|
||||
console.error(error)
|
||||
|
||||
// Better Cloudflare errors
|
||||
const cloudflareErrors = {
|
||||
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'
|
||||
}
|
||||
|
||||
const statusText = cloudflareErrors[error.response.status] || error.response.statusText
|
||||
const description = error.response.data && error.response.data.description
|
||||
? error.response.data.description
|
||||
: 'There was an error with the request, please check the console for more information.'
|
||||
|
||||
return swal(`${error.response.status} ${statusText}`, description, 'error')
|
||||
}
|
||||
|
||||
page.do = (dest, trigger) => {
|
||||
const user = page.user.value.trim()
|
||||
if (!user)
|
||||
@ -35,34 +65,29 @@ page.do = (dest, trigger) => {
|
||||
localStorage.token = response.data.token
|
||||
window.location = 'dashboard'
|
||||
}).catch(error => {
|
||||
console.error(error)
|
||||
trigger.classList.remove('is-loading')
|
||||
return swal('An error occurred!', 'There was an error with the request, please check the console for more information.', 'error')
|
||||
page.onAxiosError(error)
|
||||
})
|
||||
}
|
||||
|
||||
page.verify = () => {
|
||||
if (!page.token) return
|
||||
|
||||
axios.post('api/tokens/verify', {
|
||||
token: page.token
|
||||
}).then(response => {
|
||||
if (response.data.success === false)
|
||||
if (response.data.success === false) {
|
||||
page.unhide()
|
||||
return swal('An error occurred!', response.data.description, 'error')
|
||||
}
|
||||
|
||||
// Redirect to dashboard if token is valid
|
||||
window.location = 'dashboard'
|
||||
}).catch(error => {
|
||||
console.error(error)
|
||||
const description = error.response.data && error.response.data.description
|
||||
? error.response.data.description
|
||||
: 'There was an error with the request, please check the console for more information.'
|
||||
return swal(`${error.response.status} ${error.response.statusText}`, description, 'error')
|
||||
page.unhide()
|
||||
page.onAxiosError(error)
|
||||
})
|
||||
}
|
||||
|
||||
window.onload = () => {
|
||||
page.verify()
|
||||
|
||||
page.user = document.querySelector('#user')
|
||||
page.pass = document.querySelector('#pass')
|
||||
|
||||
@ -81,4 +106,9 @@ window.onload = () => {
|
||||
if (!form.checkValidity()) return
|
||||
page.do('register', event.currentTarget)
|
||||
})
|
||||
|
||||
if (page.token)
|
||||
page.verify()
|
||||
else
|
||||
page.unhide()
|
||||
}
|
||||
|
@ -90,12 +90,49 @@ const page = {
|
||||
albumDescMaxLength: 4000
|
||||
}
|
||||
|
||||
page.preparePage = () => {
|
||||
if (!page.token) {
|
||||
window.location = 'auth'
|
||||
return
|
||||
// Handler for regular JS errors
|
||||
page.onError = error => {
|
||||
console.error(error)
|
||||
|
||||
const content = document.createElement('div')
|
||||
content.innerHTML = `<code>${error.toString()}</code>`
|
||||
return swal({
|
||||
title: 'An error occurred!',
|
||||
icon: 'error',
|
||||
content
|
||||
})
|
||||
}
|
||||
|
||||
// Handler for Axios errors
|
||||
page.onAxiosError = error => {
|
||||
console.error(error)
|
||||
|
||||
// Better Cloudflare errors
|
||||
const cloudflareErrors = {
|
||||
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'
|
||||
}
|
||||
|
||||
const statusText = cloudflareErrors[error.response.status] || error.response.statusText
|
||||
const description = error.response.data && error.response.data.description
|
||||
? error.response.data.description
|
||||
: 'There was an error with the request, please check the console for more information.'
|
||||
|
||||
return swal(`${error.response.status} ${statusText}`, description, 'error')
|
||||
}
|
||||
|
||||
page.preparePage = () => {
|
||||
if (page.token)
|
||||
page.verifyToken(page.token, true)
|
||||
else
|
||||
window.location = 'auth'
|
||||
}
|
||||
|
||||
page.verifyToken = (token, reloadOnError) => {
|
||||
@ -113,14 +150,12 @@ page.verifyToken = (token, reloadOnError) => {
|
||||
|
||||
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(error => {
|
||||
console.error(error)
|
||||
return swal('An error occurred!', 'There was an error with the request, please check the console for more information.', 'error')
|
||||
})
|
||||
}).catch(page.onAxiosError)
|
||||
}
|
||||
|
||||
page.prepareDashboard = () => {
|
||||
@ -145,7 +180,7 @@ page.prepareDashboard = () => {
|
||||
{ selector: '#itemManageAlbums', onclick: page.getAlbums },
|
||||
{ selector: '#itemManageToken', onclick: page.changeToken },
|
||||
{ selector: '#itemChangePassword', onclick: page.changePassword },
|
||||
{ selector: '#itemLogout', onclick: page.logout, inactive: true },
|
||||
{ selector: '#itemLogout', onclick: page.logout },
|
||||
{ selector: '#itemManageUploads', onclick: page.getUploads, params: { all: true }, group: 'moderator' },
|
||||
{ selector: '#itemStatistics', onclick: page.getStatistics, group: 'admin' },
|
||||
{ selector: '#itemManageUsers', onclick: page.getUsers, group: 'admin' }
|
||||
@ -162,6 +197,7 @@ page.prepareDashboard = () => {
|
||||
// This class name isn't actually being applied fast enough
|
||||
if (page.menusContainer.classList.contains('is-loading'))
|
||||
return
|
||||
|
||||
// eslint-disable-next-line compat/compat
|
||||
itemMenus[i].onclick.call(null, Object.assign({
|
||||
trigger: event.currentTarget
|
||||
@ -191,7 +227,8 @@ page.prepareDashboard = () => {
|
||||
page.prepareShareX()
|
||||
}
|
||||
|
||||
page.logout = () => {
|
||||
page.logout = params => {
|
||||
page.updateTrigger(params.trigger, 'active')
|
||||
localStorage.removeItem(lsKeys.token)
|
||||
window.location = 'auth'
|
||||
}
|
||||
@ -308,11 +345,17 @@ page.fadeAndScroll = content => {
|
||||
clearTimeout(page.fadingIn)
|
||||
page.dom.classList.remove('fade-in')
|
||||
}
|
||||
|
||||
page.dom.classList.add('fade-in')
|
||||
page.fadingIn = setTimeout(() => {
|
||||
page.dom.classList.remove('fade-in')
|
||||
}, 500)
|
||||
page.dom.scrollIntoView(true)
|
||||
|
||||
page.dom.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'start',
|
||||
inline: 'nearest'
|
||||
})
|
||||
}
|
||||
|
||||
page.switchPage = (action, element) => {
|
||||
@ -409,14 +452,14 @@ page.getUploads = (params = {}) => {
|
||||
<input id="filters" class="input is-small" type="text" placeholder="Filters" value="${params.filters || ''}">
|
||||
</div>
|
||||
<div class="control">
|
||||
<button type="button" class="button is-small is-info" title="Help?" data-action="filters-help">
|
||||
<button type="button" class="button is-small is-primary is-outlined" title="Help?" data-action="filters-help">
|
||||
<span class="icon">
|
||||
<i class="icon-help-circled"></i>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="control">
|
||||
<button type="submit" class="button is-small is-info" title="Filter uploads" data-action="filter-uploads">
|
||||
<button type="submit" class="button is-small is-info is-outlined" title="Filter uploads" data-action="filter-uploads">
|
||||
<span class="icon">
|
||||
<i class="icon-filter"></i>
|
||||
</span>
|
||||
@ -436,7 +479,7 @@ page.getUploads = (params = {}) => {
|
||||
<input id="jumpToPage" class="input is-small" type="number" value="${params.pageNum + 1}">
|
||||
</div>
|
||||
<div class="control">
|
||||
<button type="submit" class="button is-small is-info" title="Jump to page" data-action="jump-to-page">
|
||||
<button type="submit" class="button is-small is-info is-outlined" title="Jump to page" data-action="jump-to-page">
|
||||
<span class="icon">
|
||||
<i class="icon-paper-plane"></i>
|
||||
</span>
|
||||
@ -452,30 +495,30 @@ page.getUploads = (params = {}) => {
|
||||
<div class="columns">
|
||||
<div class="column is-hidden-mobile"></div>
|
||||
<div class="column has-text-centered">
|
||||
<a class="button is-small is-danger" title="List view" data-action="view-list">
|
||||
<a class="button is-small is-danger is-outlined" title="List view" data-action="view-list">
|
||||
<span class="icon">
|
||||
<i class="icon-th-list"></i>
|
||||
</span>
|
||||
</a>
|
||||
<a class="button is-small is-danger" title="Thumbs view" data-action="view-thumbs">
|
||||
<a class="button is-small is-danger is-outlined" title="Thumbs view" data-action="view-thumbs">
|
||||
<span class="icon">
|
||||
<i class="icon-th-large"></i>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="column has-text-right">
|
||||
<a class="button is-small is-info" title="Clear selection" data-action="clear-selection">
|
||||
<a class="button is-small is-info is-outlined" title="Clear selection" data-action="clear-selection">
|
||||
<span class="icon">
|
||||
<i class="icon-cancel"></i>
|
||||
</span>
|
||||
</a>
|
||||
${params.all ? '' : `
|
||||
<a class="button is-small is-warning" title="Bulk add to album" data-action="add-selected-uploads-to-album">
|
||||
<a class="button is-small is-warning is-outlined" title="Bulk add to album" data-action="add-selected-uploads-to-album">
|
||||
<span class="icon">
|
||||
<i class="icon-plus"></i>
|
||||
</span>
|
||||
</a>`}
|
||||
<a class="button is-small is-danger" title="Bulk delete" data-action="bulk-delete-uploads">
|
||||
<a class="button is-small is-danger is-outlined" title="Bulk delete" data-action="bulk-delete-uploads">
|
||||
<span class="icon">
|
||||
<i class="icon-trash"></i>
|
||||
</span>
|
||||
@ -546,7 +589,6 @@ page.getUploads = (params = {}) => {
|
||||
${controls}
|
||||
<div id="table" class="columns is-multiline is-mobile is-centered">
|
||||
</div>
|
||||
<hr>
|
||||
${pagination}
|
||||
`
|
||||
|
||||
@ -623,7 +665,6 @@ page.getUploads = (params = {}) => {
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<hr>
|
||||
${pagination}
|
||||
`
|
||||
|
||||
@ -642,23 +683,23 @@ page.getUploads = (params = {}) => {
|
||||
<td>${upload.prettyDate}</td>
|
||||
${hasExpiryDateColumn ? `<td>${upload.prettyExpiryDate || '-'}</td>` : ''}
|
||||
<td class="controls has-text-right">
|
||||
<a class="button is-small is-primary" title="${upload.thumb ? 'Display preview' : 'File can\'t be previewed'}" data-action="display-preview"${upload.thumb ? '' : ' disabled'}>
|
||||
<a class="button is-small is-primary is-outlined" title="${upload.thumb ? 'Display preview' : 'File can\'t be previewed'}" data-action="display-preview"${upload.thumb ? '' : ' disabled'}>
|
||||
<span class="icon">
|
||||
<i class="${upload.type !== 'other' ? `icon-${upload.type}` : 'icon-doc-inv'}"></i>
|
||||
</span>
|
||||
</a>
|
||||
<a class="button is-small is-info clipboard-js" title="Copy link to clipboard" data-clipboard-text="${upload.file}">
|
||||
<a class="button is-small is-info is-outlined clipboard-js" title="Copy link to clipboard" data-clipboard-text="${upload.file}">
|
||||
<span class="icon">
|
||||
<i class="icon-clipboard"></i>
|
||||
</span>
|
||||
</a>
|
||||
${params.all ? '' : `
|
||||
<a class="button is-small is-warning" title="Add to album" data-action="add-to-album">
|
||||
<a class="button is-small is-warning is-outlined" title="Add to album" data-action="add-to-album">
|
||||
<span class="icon">
|
||||
<i class="icon-plus"></i>
|
||||
</span>
|
||||
</a>`}
|
||||
<a class="button is-small is-danger" title="Delete" data-action="delete-upload">
|
||||
<a class="button is-small is-danger is-outlined" title="Delete" data-action="delete-upload">
|
||||
<span class="icon">
|
||||
<i class="icon-trash"></i>
|
||||
</span>
|
||||
@ -686,9 +727,8 @@ page.getUploads = (params = {}) => {
|
||||
page.views.uploadsAll.filters = params.filters
|
||||
page.views[page.currentView].pageNum = files.length ? params.pageNum : 0
|
||||
}).catch(error => {
|
||||
console.error(error)
|
||||
page.updateTrigger(params.trigger)
|
||||
return swal('An error occurred!', 'There was an error with the request, please check the console for more information.', 'error')
|
||||
page.onAxiosError(error)
|
||||
})
|
||||
}
|
||||
|
||||
@ -725,7 +765,7 @@ page.displayPreview = id => {
|
||||
div.innerHTML += `
|
||||
<div class="field has-text-centered">
|
||||
<div class="controls">
|
||||
<a id="swalOriginal" type="button" class="button is-info is-fullwidth" data-original="${file.original}">
|
||||
<a id="swalOriginal" type="button" class="button is-info is-outlined is-fullwidth" data-original="${file.original}">
|
||||
<span class="icon">
|
||||
<i class="icon-arrows-cw"></i>
|
||||
</span>
|
||||
@ -1006,7 +1046,7 @@ page.deleteUploadsByNames = (params = {}) => {
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<button type="submit" id="submitBulkDelete" class="button is-danger is-fullwidth">
|
||||
<button type="submit" id="submitBulkDelete" class="button is-danger is-outlined is-fullwidth">
|
||||
<span class="icon">
|
||||
<i class="icon-trash"></i>
|
||||
</span>
|
||||
@ -1110,10 +1150,7 @@ page.postBulkDeleteUploads = (params = {}) => {
|
||||
|
||||
if (typeof params.cb === 'function')
|
||||
params.cb(failed)
|
||||
}).catch(error => {
|
||||
console.error(error)
|
||||
swal('An error occurred!', 'There was an error with the request, please check the console for more information.', 'error')
|
||||
})
|
||||
}).catch(page.onAxiosError)
|
||||
})
|
||||
}
|
||||
|
||||
@ -1209,13 +1246,7 @@ page.addUploadsToAlbum = (ids, callback) => {
|
||||
|
||||
swal('Woohoo!', `Successfully ${albumid < 0 ? 'removed' : 'added'} ${added} ${suffix} ${albumid < 0 ? 'from' : 'to'} the album.`, 'success')
|
||||
callback(add.data.failed)
|
||||
}).catch(error => {
|
||||
console.error(error)
|
||||
return swal('An error occurred!', 'There was an error with the request, please check the console for more information.', 'error')
|
||||
})
|
||||
}).catch(error => {
|
||||
console.error(error)
|
||||
return swal('An error occurred!', 'There was an error with the request, please check the console for more information.', 'error')
|
||||
}).catch(page.onAxiosError)
|
||||
})
|
||||
|
||||
// Get albums list then update content of swal
|
||||
@ -1241,14 +1272,12 @@ page.addUploadsToAlbum = (ids, callback) => {
|
||||
|
||||
select.getElementsByTagName('option')[1].innerHTML = 'Choose an album'
|
||||
select.removeAttribute('disabled')
|
||||
}).catch(error => {
|
||||
console.error(error)
|
||||
return swal('An error occurred!', 'There was an error with the request, please check the console for more information.', 'error')
|
||||
})
|
||||
}).catch(page.onAxiosError)
|
||||
}
|
||||
|
||||
page.getAlbums = (params = {}) => {
|
||||
page.updateTrigger(params.trigger, 'loading')
|
||||
|
||||
axios.get('api/albums').then(response => {
|
||||
if (!response) return
|
||||
|
||||
@ -1279,7 +1308,7 @@ page.getAlbums = (params = {}) => {
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<button type="submit" id="submitAlbum" class="button is-info is-fullwidth" data-action="submit-album">
|
||||
<button type="submit" id="submitAlbum" class="button is-info is-outlined is-fullwidth" data-action="submit-album">
|
||||
<span class="icon">
|
||||
<i class="icon-paper-plane"></i>
|
||||
</span>
|
||||
@ -1333,22 +1362,22 @@ page.getAlbums = (params = {}) => {
|
||||
<td>${album.prettyDate}</td>
|
||||
<td><a ${album.public ? `href="${albumUrl}"` : 'class="is-linethrough"'} target="_blank" rel="noopener">${albumUrl}</a></td>
|
||||
<td class="has-text-right" data-id="${album.id}">
|
||||
<a class="button is-small is-primary" title="Edit album" data-action="edit-album">
|
||||
<a class="button is-small is-primary is-outlined" title="Edit album" data-action="edit-album">
|
||||
<span class="icon is-small">
|
||||
<i class="icon-pencil"></i>
|
||||
</span>
|
||||
</a>
|
||||
<a class="button is-small is-info clipboard-js" title="Copy link to clipboard" ${album.public ? `data-clipboard-text="${albumUrl}"` : 'disabled'}>
|
||||
<a class="button is-small is-info is-outlined clipboard-js" title="Copy link to clipboard" ${album.public ? `data-clipboard-text="${albumUrl}"` : 'disabled'}>
|
||||
<span class="icon is-small">
|
||||
<i class="icon-clipboard"></i>
|
||||
</span>
|
||||
</a>
|
||||
<a class="button is-small is-warning" title="Download album" ${album.download ? `href="api/album/zip/${album.identifier}?v=${album.editedAt}"` : 'disabled'}>
|
||||
<a class="button is-small is-warning is-outlined" title="Download album" ${album.download ? `href="api/album/zip/${album.identifier}?v=${album.editedAt}"` : 'disabled'}>
|
||||
<span class="icon is-small">
|
||||
<i class="icon-download"></i>
|
||||
</span>
|
||||
</a>
|
||||
<a class="button is-small is-danger" title="Delete album" data-action="delete-album">
|
||||
<a class="button is-small is-danger is-outlined" title="Delete album" data-action="delete-album">
|
||||
<span class="icon is-small">
|
||||
<i class="icon-trash"></i>
|
||||
</span>
|
||||
@ -1361,9 +1390,8 @@ page.getAlbums = (params = {}) => {
|
||||
page.fadeAndScroll()
|
||||
page.updateTrigger(params.trigger, 'active')
|
||||
}).catch(error => {
|
||||
console.error(error)
|
||||
page.updateTrigger(params.trigger)
|
||||
return swal('An error occurred!', 'There was an error with the request, please check the console for more information.', 'error')
|
||||
page.onAxiosError(error)
|
||||
})
|
||||
}
|
||||
|
||||
@ -1450,10 +1478,7 @@ page.editAlbum = id => {
|
||||
|
||||
page.getAlbumsSidebar()
|
||||
page.getAlbums()
|
||||
}).catch(error => {
|
||||
console.error(error)
|
||||
return swal('An error occurred!', 'There was an error with the request, please check the console for more information.', 'error')
|
||||
})
|
||||
}).catch(page.onAxiosError)
|
||||
})
|
||||
}
|
||||
|
||||
@ -1495,15 +1520,13 @@ page.deleteAlbum = id => {
|
||||
swal('Deleted!', 'Your album has been deleted.', 'success')
|
||||
page.getAlbumsSidebar()
|
||||
page.getAlbums()
|
||||
}).catch(error => {
|
||||
console.error(error)
|
||||
return swal('An error occurred!', 'There was an error with the request, please check the console for more information.', 'error')
|
||||
})
|
||||
}).catch(page.onAxiosError)
|
||||
})
|
||||
}
|
||||
|
||||
page.submitAlbum = element => {
|
||||
page.updateTrigger(element, 'loading')
|
||||
|
||||
axios.post('api/albums', {
|
||||
name: document.querySelector('#albumName').value.trim(),
|
||||
description: document.querySelector('#albumDescription').value.trim()
|
||||
@ -1522,9 +1545,8 @@ page.submitAlbum = element => {
|
||||
page.getAlbumsSidebar()
|
||||
page.getAlbums()
|
||||
}).catch(error => {
|
||||
console.error(error)
|
||||
page.updateTrigger(element)
|
||||
return swal('An error occurred!', 'There was an error with the request, please check the console for more information.', 'error')
|
||||
page.onAxiosError(error)
|
||||
})
|
||||
}
|
||||
|
||||
@ -1571,35 +1593,22 @@ page.getAlbumsSidebar = () => {
|
||||
li.appendChild(a)
|
||||
albumsContainer.appendChild(li)
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error(error)
|
||||
return swal('An error occurred!', 'There was an error with the request, please check the console for more information.', 'error')
|
||||
})
|
||||
}).catch(page.onAxiosError)
|
||||
}
|
||||
|
||||
page.changeToken = (params = {}) => {
|
||||
page.updateTrigger(params.trigger, 'loading')
|
||||
axios.get('api/tokens').then(response => {
|
||||
if (response.data.success === false)
|
||||
if (response.data.description === 'No token provided') {
|
||||
return page.verifyToken(page.token)
|
||||
} else {
|
||||
page.updateTrigger(params.trigger)
|
||||
return swal('An error occurred!', response.data.description, 'error')
|
||||
}
|
||||
|
||||
page.dom.innerHTML = `
|
||||
<div class="field">
|
||||
<label class="label">Your current token:</label>
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<input id="token" readonly class="input" type="text" placeholder="Your token" value="${response.data.token}">
|
||||
<input id="token" readonly class="input" type="text" placeholder="Your token" value="${page.token}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<a id="getNewToken" class="button is-info is-fullwidth">
|
||||
<a id="getNewToken" class="button is-info is-outlined is-fullwidth">
|
||||
<span class="icon">
|
||||
<i class="icon-arrows-cw"></i>
|
||||
</span>
|
||||
@ -1635,16 +1644,10 @@ page.changeToken = (params = {}) => {
|
||||
page.changeToken()
|
||||
})
|
||||
}).catch(error => {
|
||||
console.error(error)
|
||||
page.updateTrigger(trigger)
|
||||
return swal('An error occurred!', 'There was an error with the request, please check the console for more information.', 'error')
|
||||
page.onAxiosError(error)
|
||||
})
|
||||
})
|
||||
}).catch(error => {
|
||||
console.error(error)
|
||||
page.updateTrigger(params.trigger)
|
||||
return swal('An error occurred!', 'There was an error with the request, please check the console for more information.', 'error')
|
||||
})
|
||||
}
|
||||
|
||||
page.changePassword = (params = {}) => {
|
||||
@ -1664,7 +1667,7 @@ page.changePassword = (params = {}) => {
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<button type="submit" id="sendChangePassword" class="button is-info is-fullwidth">
|
||||
<button type="submit" id="sendChangePassword" class="button is-info is-outlined is-fullwidth">
|
||||
<span class="icon">
|
||||
<i class="icon-paper-plane"></i>
|
||||
</span>
|
||||
@ -1693,15 +1696,15 @@ page.sendNewPassword = (pass, element) => {
|
||||
page.updateTrigger(element, 'loading')
|
||||
|
||||
axios.post('api/password/change', { password: pass }).then(response => {
|
||||
page.updateTrigger(element)
|
||||
|
||||
if (response.data.success === false)
|
||||
if (response.data.description === 'No token provided') {
|
||||
return page.verifyToken(page.token)
|
||||
} else {
|
||||
page.updateTrigger(element)
|
||||
return swal('An error occurred!', response.data.description, 'error')
|
||||
}
|
||||
|
||||
page.updateTrigger(element)
|
||||
swal({
|
||||
title: 'Woohoo!',
|
||||
text: 'Your password was successfully changed.',
|
||||
@ -1710,9 +1713,8 @@ page.sendNewPassword = (pass, element) => {
|
||||
page.changePassword()
|
||||
})
|
||||
}).catch(error => {
|
||||
console.error(error)
|
||||
page.updateTrigger(element)
|
||||
return swal('An error occurred!', 'There was an error with the request, please check the console for more information.', 'error')
|
||||
page.onAxiosError(error)
|
||||
})
|
||||
}
|
||||
|
||||
@ -1756,7 +1758,7 @@ page.getUsers = (params = {}) => {
|
||||
<input id="jumpToPage" class="input is-small" type="number" value="${params.pageNum + 1}">
|
||||
</div>
|
||||
<div class="control">
|
||||
<button type="submit" class="button is-small is-info" title="Jump to page" data-action="jump-to-page">
|
||||
<button type="submit" class="button is-small is-info is-outlined" title="Jump to page" data-action="jump-to-page">
|
||||
<span class="icon">
|
||||
<i class="icon-paper-plane"></i>
|
||||
</span>
|
||||
@ -1854,22 +1856,22 @@ page.getUsers = (params = {}) => {
|
||||
<td>${page.getPrettyBytes(user.usage)}</td>
|
||||
<td>${displayGroup}</td>
|
||||
<td class="controls has-text-right">
|
||||
<a class="button is-small is-primary" title="Edit user" data-action="edit-user">
|
||||
<a class="button is-small is-primary is-outlined" title="Edit user" data-action="edit-user">
|
||||
<span class="icon">
|
||||
<i class="icon-pencil"></i>
|
||||
</span>
|
||||
</a>
|
||||
<a class="button is-small is-info" title="${user.uploads ? 'View uploads' : 'User doesn\'t have uploads'}" data-action="view-user-uploads" ${user.uploads ? '' : 'disabled'}>
|
||||
<a class="button is-small is-info is-outlined" title="${user.uploads ? 'View uploads' : 'User doesn\'t have uploads'}" data-action="view-user-uploads" ${user.uploads ? '' : 'disabled'}>
|
||||
<span class="icon">
|
||||
<i class="icon-docs"></i>
|
||||
</span>
|
||||
</a>
|
||||
<a class="button is-small is-warning" title="${enabled ? 'Disable user' : 'User is disabled'}" data-action="disable-user" ${enabled ? '' : 'disabled'}>
|
||||
<a class="button is-small is-warning is-outlined" title="${enabled ? 'Disable user' : 'User is disabled'}" data-action="disable-user" ${enabled ? '' : 'disabled'}>
|
||||
<span class="icon">
|
||||
<i class="icon-hammer"></i>
|
||||
</span>
|
||||
</a>
|
||||
<a class="button is-small is-danger is-hidden" title="Delete user (WIP)" data-action="delete-user" disabled>
|
||||
<a class="button is-small is-danger is-outlined is-hidden" title="Delete user (WIP)" data-action="delete-user" disabled>
|
||||
<span class="icon">
|
||||
<i class="icon-trash"></i>
|
||||
</span>
|
||||
@ -1893,8 +1895,7 @@ page.getUsers = (params = {}) => {
|
||||
page.views.users.pageNum = response.data.users.length ? params.pageNum : 0
|
||||
}).catch(error => {
|
||||
page.updateTrigger(params.trigger)
|
||||
console.error(error)
|
||||
return swal('An error occurred!', 'There was an error with the request, please check the console for more information.', 'error')
|
||||
page.onAxiosError(error)
|
||||
})
|
||||
}
|
||||
|
||||
@ -1991,10 +1992,7 @@ page.editUser = id => {
|
||||
}
|
||||
|
||||
page.getUsers(page.views.users)
|
||||
}).catch(error => {
|
||||
console.error(error)
|
||||
return swal('An error occurred!', 'There was an error with the request, please check the console for more information.', 'error')
|
||||
})
|
||||
}).catch(page.onAxiosError)
|
||||
})
|
||||
}
|
||||
|
||||
@ -2031,10 +2029,7 @@ page.disableUser = id => {
|
||||
|
||||
swal('Success!', 'The user has been disabled.', 'success')
|
||||
page.getUsers(page.views.users)
|
||||
}).catch(error => {
|
||||
console.error(error)
|
||||
return swal('An error occurred!', 'There was an error with the request, please check the console for more information.', 'error')
|
||||
})
|
||||
}).catch(page.onAxiosError)
|
||||
})
|
||||
}
|
||||
|
||||
@ -2093,6 +2088,7 @@ page.getStatistics = (params = {}) => {
|
||||
return swal('An error occurred!', 'You can not do this!', 'error')
|
||||
|
||||
page.updateTrigger(params.trigger, 'loading')
|
||||
|
||||
const url = 'api/stats'
|
||||
axios.get(url).then(response => {
|
||||
if (response.data.success === false)
|
||||
@ -2145,13 +2141,13 @@ page.getStatistics = (params = {}) => {
|
||||
`
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
rows = `
|
||||
<tr>
|
||||
<td>Error parsing response. Try again?</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
`
|
||||
page.onError(error)
|
||||
}
|
||||
|
||||
content += `
|
||||
@ -2175,16 +2171,13 @@ page.getStatistics = (params = {}) => {
|
||||
page.fadeAndScroll()
|
||||
page.updateTrigger(params.trigger, 'active')
|
||||
}).catch(error => {
|
||||
console.error(error)
|
||||
page.updateTrigger(params.trigger)
|
||||
const description = error.response.data && error.response.data.description
|
||||
? error.response.data && error.response.data.description
|
||||
: 'There was an error with the request, please check the console for more information.'
|
||||
return swal('An error occurred!', description, 'error')
|
||||
page.onAxiosError(error)
|
||||
})
|
||||
}
|
||||
|
||||
window.onload = () => {
|
||||
// Polyfill Object.assign()
|
||||
// eslint-disable-next-line compat/compat
|
||||
if (typeof Object.assign !== 'function')
|
||||
// Must be writable: true, enumerable: false, configurable: true
|
||||
@ -2226,10 +2219,7 @@ window.onload = () => {
|
||||
return swal('Copied!', 'The link has been copied to clipboard.', 'success')
|
||||
})
|
||||
|
||||
page.clipboardJS.on('error', 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.clipboardJS.on('error', page.onError)
|
||||
|
||||
page.lazyLoad = new LazyLoad()
|
||||
}
|
||||
|
@ -52,10 +52,8 @@ const page = {
|
||||
albumDescMaxLength: 4000
|
||||
}
|
||||
|
||||
// Error handler for all API requests on init
|
||||
// Handler for errors during initialization
|
||||
page.onInitError = error => {
|
||||
console.error(error)
|
||||
|
||||
// Hide these elements
|
||||
document.querySelector('#albumDiv').classList.add('is-hidden')
|
||||
document.querySelector('#tabs').classList.add('is-hidden')
|
||||
@ -73,11 +71,30 @@ page.onInitError = error => {
|
||||
location.reload()
|
||||
})
|
||||
|
||||
// Defer to the other handler if not API errors
|
||||
if (!error.response)
|
||||
return page.onUnexpectedError(error, true)
|
||||
if (error.response)
|
||||
page.onAxiosError(error)
|
||||
else
|
||||
page.onError(error)
|
||||
}
|
||||
|
||||
// Better error messages for Cloudflare errors
|
||||
// Handler for regular JS errors
|
||||
page.onError = error => {
|
||||
console.error(error)
|
||||
|
||||
const content = document.createElement('div')
|
||||
content.innerHTML = `<code>${error.toString()}</code>`
|
||||
return swal({
|
||||
title: 'An error occurred!',
|
||||
icon: 'error',
|
||||
content
|
||||
})
|
||||
}
|
||||
|
||||
// Handler for Axios errors
|
||||
page.onAxiosError = error => {
|
||||
console.error(error)
|
||||
|
||||
// Better Cloudflare errors
|
||||
const cloudflareErrors = {
|
||||
520: 'Unknown Error',
|
||||
521: 'Web Server Is Down',
|
||||
@ -89,29 +106,15 @@ page.onInitError = error => {
|
||||
527: 'Railgun Error',
|
||||
530: 'Origin DNS Error'
|
||||
}
|
||||
|
||||
const statusText = cloudflareErrors[error.response.status] || error.response.statusText
|
||||
const description = error.response.data && error.response.data.description
|
||||
? error.response.data.description
|
||||
: 'Please check the console for more information.'
|
||||
: 'There was an error with the request, please check the console for more information.'
|
||||
|
||||
return swal(`${error.response.status} ${statusText}`, description, 'error')
|
||||
}
|
||||
|
||||
// Error handler for all other unexpected errors
|
||||
page.onUnexpectedError = (error, skipLog) => {
|
||||
if (!skipLog) console.error(error)
|
||||
|
||||
if (error.response)
|
||||
return swal('An error occurred!', 'There was an error with the request, please check the console for more information.', 'error')
|
||||
|
||||
const content = document.createElement('div')
|
||||
content.innerHTML = `<code>${error.toString()}</code>`
|
||||
return swal({
|
||||
title: 'An error occurred!',
|
||||
icon: 'error',
|
||||
content
|
||||
})
|
||||
}
|
||||
|
||||
page.checkIfPublic = onFailure => {
|
||||
let renderShown = false
|
||||
return axios.get('api/check', {
|
||||
@ -164,7 +167,7 @@ page.verifyToken = (token, reloadOnError) => {
|
||||
location.reload()
|
||||
})
|
||||
|
||||
localStorage.token = token
|
||||
localStorage[lsKeys.token] = token
|
||||
page.token = token
|
||||
return page.prepareUpload()
|
||||
}).catch(page.onInitError)
|
||||
@ -277,7 +280,7 @@ page.prepareDropzone = () => {
|
||||
const div = document.createElement('div')
|
||||
div.className = 'control is-expanded'
|
||||
div.innerHTML = `
|
||||
<div id="dropzone" class="button is-danger is-fullwidth is-unselectable">
|
||||
<div id="dropzone" class="button is-danger is-outlined is-fullwidth is-unselectable">
|
||||
<span class="icon">
|
||||
<i class="icon-upload-cloud"></i>
|
||||
</span>
|
||||
@ -577,7 +580,7 @@ page.createAlbum = () => {
|
||||
option.selected = true
|
||||
|
||||
swal('Woohoo!', 'Album was created successfully.', 'success')
|
||||
}).catch(page.onUnexpectedError)
|
||||
}).catch(page.onError)
|
||||
})
|
||||
}
|
||||
|
||||
@ -758,7 +761,7 @@ window.onload = () => {
|
||||
return swal('Copied!', 'The link has been copied to clipboard.', 'success')
|
||||
})
|
||||
|
||||
page.clipboardJS.on('error', page.onUnexpectedError)
|
||||
page.clipboardJS.on('error', page.onError)
|
||||
|
||||
page.lazyLoad = new LazyLoad({
|
||||
elements_selector: '.field.uploads img'
|
||||
|
1
todo.md
1
todo.md
@ -19,6 +19,7 @@ Normal priority:
|
||||
* [x] Display renders after API check.
|
||||
* [ ] Enforce pass min/max lengths in dashboard's change password form.
|
||||
* [ ] Add a copy all links to clipboard when there are more than 2 uploads in history.
|
||||
* [*] Update fb_share.png.
|
||||
|
||||
Low priority:
|
||||
|
||||
|
@ -15,9 +15,9 @@
|
||||
v3: CSS and JS files (libs such as bulma, lazyload, etc).
|
||||
v4: Renders in /public/render/* directories (to be used by render.js).
|
||||
#}
|
||||
{% set v1 = "yUGjkyDjqU" %}
|
||||
{% set v1 = "YFsTqC68Bc" %}
|
||||
{% set v2 = "hiboQUzAzp" %}
|
||||
{% set v3 = "iDzQ0dov5j" %}
|
||||
{% set v3 = "YFsTqC68Bc" %}
|
||||
{% set v4 = "S3TAWpPeFS" %}
|
||||
|
||||
{#
|
||||
|
@ -18,7 +18,14 @@
|
||||
|
||||
{% block content %}
|
||||
{{ super() }}
|
||||
<section id="login" class="hero is-fullheight">
|
||||
<section id="loader" class="hero is-fullheight">
|
||||
<div class="hero-body">
|
||||
<div class="container has-text-centered">
|
||||
<span class="loader is-inline-block"></span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section id="login" class="hero is-fullheight is-hidden">
|
||||
<div class="hero-body">
|
||||
<div class="container">
|
||||
<div class="columns is-centered">
|
||||
@ -43,7 +50,7 @@
|
||||
</div>
|
||||
<div class="field is-grouped is-grouped-right">
|
||||
<div class="control">
|
||||
<button id="registerBtn" type="button" class="button">
|
||||
<button id="registerBtn" type="button" class="button is-primary is-outlined">
|
||||
<span class="icon">
|
||||
<i class="icon-user-plus"></i>
|
||||
</span>
|
||||
@ -51,7 +58,7 @@
|
||||
</button>
|
||||
</div>
|
||||
<div class="control">
|
||||
<button id="loginBtn" type="submit" class="button is-info">
|
||||
<button id="loginBtn" type="submit" class="button is-info is-outlined">
|
||||
<span class="icon">
|
||||
<i class="icon-login"></i>
|
||||
</span>
|
||||
|
@ -17,6 +17,8 @@
|
||||
<script src="libs/axios/axios.min.js?v={{ globals.v3 }}"></script>
|
||||
<script src="libs/clipboard.js/clipboard.min.js?v={{ globals.v3 }}"></script>
|
||||
<script src="libs/lazyload/lazyload.min.js?v={{ globals.v3 }}"></script>
|
||||
{# Polyfill smooth scroll for older browsers #}
|
||||
<script src="libs/smoothscroll/smoothscroll.min.js?v={{ globals.v3 }}"></script>
|
||||
<script src="js/dashboard.js?v={{ globals.v1 }}"></script>
|
||||
<script src="js/misc/utils.js?v={{ globals.v1 }}"></script>
|
||||
{% endblock %}
|
||||
|
@ -52,7 +52,7 @@
|
||||
<div class="columns is-gapless">
|
||||
<div class="column is-hidden-mobile"></div>
|
||||
<div class="column">
|
||||
<button id="loginToUpload" class="button is-danger is-fullwidth is-loading"></button>
|
||||
<a id="loginToUpload" class="button is-danger is-outlined is-fullwidth is-loading"></a>
|
||||
<div id="albumDiv" class="field has-addons is-hidden">
|
||||
<div class="control is-expanded">
|
||||
<div class="select is-fullwidth">
|
||||
@ -62,7 +62,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="control">
|
||||
<a id="createAlbum" class="button is-info" title="Create new album">
|
||||
<a id="createAlbum" class="button is-info is-outlined" title="Create new album">
|
||||
<i class="icon-plus"></i>
|
||||
</a>
|
||||
</div>
|
||||
@ -119,7 +119,7 @@
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<a id="uploadUrls" class="button is-danger is-fullwidth is-unselectable">
|
||||
<a id="uploadUrls" class="button is-danger is-outlined is-fullwidth is-unselectable">
|
||||
<span class="icon">
|
||||
<i class="icon-upload-cloud"></i>
|
||||
</span>
|
||||
@ -202,7 +202,7 @@
|
||||
</div>
|
||||
<div class="field">
|
||||
<p class="control">
|
||||
<button id="saveConfig" type="submit" class="button is-danger is-fullwidth">
|
||||
<button id="saveConfig" type="submit" class="button is-danger is-outlined is-fullwidth">
|
||||
<span class="icon">
|
||||
<i class="icon-floppy"></i>
|
||||
</span>
|
||||
|
@ -6,9 +6,9 @@
|
||||
{% set private = config.private %}
|
||||
{% set disabledMessage -%}
|
||||
{%- if config.enableUserAccounts -%}
|
||||
Anonymous upload is disabled.
|
||||
Anonymous upload is disabled. Log in to upload.
|
||||
{%- else -%}
|
||||
Running in private mode.
|
||||
Running in private mode. Log in to upload.
|
||||
{%- endif %}
|
||||
{%- endset %}
|
||||
{% set maxSizeInt = config.uploads.maxSize | int %}
|
||||
@ -38,7 +38,7 @@
|
||||
<div class="column is-hidden-mobile"></div>
|
||||
<div class="column">
|
||||
{% if private -%}
|
||||
<a class="button is-danger is-flex" href="auth">
|
||||
<a class="button is-danger is-outlined is-fullwidth" href="auth">
|
||||
{{ disabledMessage }}
|
||||
</a>
|
||||
{%- else -%}
|
||||
@ -50,7 +50,7 @@
|
||||
</div>
|
||||
<div class="field">
|
||||
<p class="control">
|
||||
<input type="submit" class="button is-danger is-fullwidth" value="Upload">
|
||||
<input type="submit" class="button is-danger is-outlined is-fullwidth" value="Upload">
|
||||
</p>
|
||||
<p class="help">
|
||||
Files uploaded through this form will not be associated with your account, if you have any.
|
||||
|
Loading…
Reference in New Issue
Block a user