From 45ae38bd66394efde06e8a994c82ec2379894346 Mon Sep 17 00:00:00 2001 From: Mark van Renswoude Date: Fri, 27 Apr 2018 17:13:18 +0200 Subject: [PATCH] Admin panel Added adding and editing of codes Added code description and message Added file sizes Added disk usage footer --- lib/api/admin.js | 84 +++++++++++++++ lib/api/upload.js | 58 +++++++++- lib/repository/code.js | 102 ++++++++++++++---- lib/repository/index.js | 2 - lib/repository/upload.js | 5 +- lib/repository/user.js | 10 +- package-lock.json | 111 +++++++++++++++++-- package.json | 8 ++ public/src/App.vue | 27 ++++- public/src/app.js | 72 ++++++++++++- public/src/locale/en.js | 30 +++++- public/src/locale/nl.js | 30 +++++- public/src/route/Code.vue | 4 +- public/src/route/Upload.vue | 46 ++++++-- public/src/route/admin/CodeDetail.vue | 127 ++++++++++++++++++++++ public/src/route/admin/Codes.vue | 69 +++++------- public/src/route/admin/Landing.vue | 150 +++++++++++++++++++++++++- public/src/route/admin/Login.vue | 6 +- public/src/route/admin/Menu.vue | 8 +- public/src/route/admin/Uploads.vue | 104 ++++++++++-------- public/src/route/admin/Users.vue | 12 +-- public/src/shared.js | 32 ++++-- 22 files changed, 915 insertions(+), 182 deletions(-) create mode 100644 public/src/route/admin/CodeDetail.vue diff --git a/lib/api/admin.js b/lib/api/admin.js index 93e9c9e..7f6ebfe 100644 --- a/lib/api/admin.js +++ b/lib/api/admin.js @@ -5,6 +5,7 @@ const jwt = require('jsonwebtoken'); const path = require('path'); const resolvePath = require('resolve-path'); const AuthTokens = require('../authtokens'); +const disk = require('diskusage'); async function checkAuthorization(req, res, repository, onVerified) @@ -84,6 +85,27 @@ module.exports = (repository) => })); + router.get('/diskspace', asyncHandler(async (req, res) => + { + await checkAuthorization(req, res, repository, async (user) => + { + disk.check(config.fileUpload.path, (err, info) => + { + if (err) + { + res.sendStatus(500); + return; + } + + res.send(info); + }); + }); + })); + + + /* + Codes + */ router.get('/codes', asyncHandler(async (req, res) => { await checkAuthorization(req, res, repository, async (user) => @@ -101,6 +123,68 @@ module.exports = (repository) => })); + router.get('/codes/:code', asyncHandler(async (req, res) => + { + await checkAuthorization(req, res, repository, async (user) => + { + var code = await repository.codes.getCode(req.params.code); + if (code === null || (code.userId !== user.userId && !user.hasAuth(AuthTokens.ViewAllCodes))) + { + res.sendStatus(404); + return; + } + + var user = await repository.users.getUser(code.userId); + if (user !== null) + code.username = user.name; + + res.send(code); + }); + })); + + + router.post('/codes', asyncHandler(async (req, res) => + { + await checkAuthorization(req, res, repository, async (user) => + { + var postedCode = req.body; + + if (postedCode.id) + { + var code = await repository.codes.getCode(postedCode.id); + if (code === null || (code.userId !== user.userId && !user.hasAuth(AuthTokens.ViewAllCodes))) + { + res.sendStatus(404); + return; + } + + await repository.codes.updateCode({ + id: postedCode.id, + expiration: postedCode.expiration, + description: postedCode.description, + message: postedCode.message + }); + + res.sendStatus(200); + } + else + { + var codeId = await repository.codes.addCode({ + userId: user.id, + expiration: postedCode.expiration, + description: postedCode.description, + message: postedCode.message + }); + } + + res.send(codeId); + }); + })); + + + /* + Uploads + */ router.get('/uploads', asyncHandler(async (req, res) => { await checkAuthorization(req, res, repository, async (user) => diff --git a/lib/api/upload.js b/lib/api/upload.js index e6c4049..0614445 100644 --- a/lib/api/upload.js +++ b/lib/api/upload.js @@ -2,6 +2,9 @@ const config = require('../../config'); const express = require('express'); const asyncHandler = require('express-async-handler'); const jwt = require('jsonwebtoken'); +const resolvePath = require('resolve-path'); +const fs = require('fs'); +const async = require('async'); async function checkAuthorization(req, res, onVerified) @@ -23,7 +26,7 @@ async function checkAuthorization(req, res, onVerified) return; } - if (decoded.codeUserId) + if (decoded.code) await onVerified(decoded); else res.sendStatus(400); @@ -43,6 +46,30 @@ module.exports = (repository, tusServer) => var router = express.Router(); // Upload API + router.get('/message/:code', asyncHandler(async (req, res) => + { + var code = await repository.codes.getCode(req.params.code); + if (code === null) + { + res.sendStatus(404); + return; + } + + if (!code.messageHTML) + { + res.sendStatus(204); + return; + } + + var user = await repository.users.getUser(code.userId); + var name = user !== null ? user.name : null; + + res.send({ + name: name, + message: code.messageHTML + }); + })); + router.post('/complete', asyncHandler(async (req, res) => { if (!req.body.files) @@ -54,8 +81,33 @@ module.exports = (repository, tusServer) => await checkAuthorization(req, res, async (decoded) => { var expiration = null; // TODO set expiration properties - var uploadId = await repository.uploads.addUpload(decoded.codeUserId, req.body.files, expiration); - res.send({ id: uploadId }); + + async.each(req.body.files, (item, callback) => + { + if (!item.id) + { + callback(); + return; + } + + var fullpath = resolvePath(config.fileUpload.path, item.id); + fs.stat(fullpath, (err, stats) => + { + item.size = stats.size; + callback(); + }); + }, + async (err) => + { + if (err) + { + res.sendStatus(500); + return; + } + + var uploadId = await repository.uploads.addUpload(decoded.codeUserId, decoded.code, req.body.files, expiration); + res.send({ id: uploadId }); + }); }); })); diff --git a/lib/repository/code.js b/lib/repository/code.js index a91a9fc..fde2cc6 100644 --- a/lib/repository/code.js +++ b/lib/repository/code.js @@ -1,4 +1,10 @@ const _ = require('lodash'); +const retry = require('async-retry'); +const shortid = require('shortid'); +const markdown = require('markdown').markdown; + + +shortid.characters('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-.'); class Code @@ -11,6 +17,9 @@ class Code self.userId = values.userId || null; self.created = values.created || new Date(); self.expiration = values.expiration || null; + self.description = values.description || null; + self.message = values.message || null; + self.messageHTML = values.messageHTML || null; } } @@ -30,20 +39,7 @@ class CodeRepository return new Promise((resolve, reject) => { - // Initialize database if empty - self.store.count({}, (err, count) => - { - if (err) - { - reject(err); - return; - } - - if (count == 0) - self.addUser('admin', null, 'test', null); - - resolve(); - }); + resolve(); }); } @@ -68,25 +64,28 @@ class CodeRepository } - async addCode(userId, expiration) + async addCode(code) { var self = this; return await retry(async bail => { - var code = shortid.generate(); + var codeId = shortid.generate(); - if ((await self.findCodeUserId(code)) !== null) - throw new Error('Code ' + code + ' already exists'); + if ((await self.findCodeUserId(codeId)) !== null) + throw new Error('Code ' + codeId + ' already exists'); self.store.insert({ - _id: code, - userId: userId, - created: new Date(), - expiration: expiration + _id: codeId, + userId: code.userId, + created: code.created || new Date(), + expiration: code.expiration, + description: code.description, + message: code.message, + messageHTML: self.getMessageHTML(code.message) }) - return code; + return codeId; }, { retries: 100, minTimeout: 0, @@ -95,6 +94,37 @@ class CodeRepository } + updateCode(code) + { + var self = this; + + return new Promise((resolve, reject) => + { + self.store.update({ _id: code.id }, { $set: { + expiration: code.expiration, + description: code.description, + message: code.message, + messageHTML: self.getMessageHTML(code.message) + }}, + (err, numAffected) => + { + if (err) + { + reject(err); + return; + } + + if (numAffected == 0) + { + reject(); + } + + resolve(); + }); + }); + } + + getCodes(userId) { var self = this; @@ -113,6 +143,32 @@ class CodeRepository }); }); } + + + getCode(codeId) + { + var self = this; + + return new Promise((resolve, reject) => + { + self.store.findOne({ _id: codeId }, (err, doc) => + { + if (err) + { + reject(err); + return; + } + + resolve(doc !== null ? new Code(doc) : null); + }); + }); + } + + + getMessageHTML(message) + { + return message !== null ? markdown.toHTML(message) : null; + } } diff --git a/lib/repository/index.js b/lib/repository/index.js index 0359a51..545215a 100644 --- a/lib/repository/index.js +++ b/lib/repository/index.js @@ -3,8 +3,6 @@ const _ = require('lodash'); const path = require('path'); const mkdirp = require('mkdirp'); const Datastore = require('nedb'); -const shortid = require('shortid'); -const retry = require('async-retry'); const UserRepository = require('./user').UserRepository; const CodeRepository = require('./code').CodeRepository; diff --git a/lib/repository/upload.js b/lib/repository/upload.js index e456f08..3098425 100644 --- a/lib/repository/upload.js +++ b/lib/repository/upload.js @@ -49,7 +49,7 @@ class UploadRepository } - addUpload(userId, files, expiration) + addUpload(userId, code, files, expiration) { var self = this; @@ -58,10 +58,11 @@ class UploadRepository var upload = { created: new Date(), userId: userId, + code: code, expiration: expiration, files: _.map(_.filter(files, (file) => file.hasOwnProperty('id') && file.hasOwnProperty('name')), - (file) => { return { id: file.id, name: file.name } }) + (file) => { return { id: file.id, name: file.name, size: file.size } }) }; if (upload.files.length) diff --git a/lib/repository/user.js b/lib/repository/user.js index 09bf4a9..01198a8 100644 --- a/lib/repository/user.js +++ b/lib/repository/user.js @@ -91,7 +91,7 @@ class UserRepository if (doc == null) { - resolve(false); + resolve(null); return; } @@ -121,13 +121,7 @@ class UserRepository return; } - if (doc == null) - { - resolve(false); - return; - } - - resolve(new User(doc)); + resolve(doc !== null ? new User(doc) : null); }); }); } diff --git a/package-lock.json b/package-lock.json index d78eef3..5830ed4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,32 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@fortawesome/fontawesome": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome/-/fontawesome-1.1.5.tgz", + "integrity": "sha512-WAgbcVs7/YTxq7RK/dhyoJPzaIZpOQnStyO5s1sj0rZa0J1ScXYoGPmsP1ec6qM/BhDjRVB228xr2DiCPTHRCA==", + "requires": { + "@fortawesome/fontawesome-common-types": "0.1.4" + } + }, + "@fortawesome/fontawesome-common-types": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.1.4.tgz", + "integrity": "sha512-JEgIoqh9HAQWul8CVLrOmWUI8nUQfCJZygRJ1Cr2H/O3+pCpVszW199cIpc7k7Nr1HJtLD77AUZVnxaKllx7AQ==" + }, + "@fortawesome/fontawesome-free-solid": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free-solid/-/fontawesome-free-solid-5.0.10.tgz", + "integrity": "sha512-RFxCG49xJcU7NwcEjAnJaerlzCzCX5sx9HdbHt3E1zML2skwmAZ2PwqyEfuT0iW2ZfYyx6SU1Zf0GUa7FL6f/A==", + "requires": { + "@fortawesome/fontawesome-common-types": "0.1.4" + } + }, + "@fortawesome/vue-fontawesome": { + "version": "0.0.22", + "resolved": "https://registry.npmjs.org/@fortawesome/vue-fontawesome/-/vue-fontawesome-0.0.22.tgz", + "integrity": "sha512-KKQ1hVTWkXRaSGVMNI/dZsznVJIZ8dqLA2J6X8nZAIg7F2Gs1zIzBC9qxOKm+gktFAJU2aCCR7BTCoZIdjvGNw==" + }, "@sindresorhus/is": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.7.0.tgz", @@ -309,10 +335,12 @@ "dev": true }, "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", - "dev": true + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.0.tgz", + "integrity": "sha512-xAfGg1/NTLBBKlHFmnd7PlmUW9KhVQIUuSrYem9xzFUZy13ScvtyGGejaae9iAVRiRq9+Cx7DPFaAAhCpyxyPw==", + "requires": { + "lodash": "4.17.5" + } }, "async-each": { "version": "1.0.1", @@ -2484,6 +2512,15 @@ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" }, + "cookie-parser": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.3.tgz", + "integrity": "sha1-D+MfoZ0AC5X0qt8fU/3CuKIDuqU=", + "requires": { + "cookie": "0.3.1", + "cookie-signature": "1.0.6" + } + }, "cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", @@ -2912,6 +2949,14 @@ "randombytes": "2.0.6" } }, + "diskusage": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/diskusage/-/diskusage-0.2.4.tgz", + "integrity": "sha512-XCLBopqnV6FUG/DdphILleiubqVERvF1ZRqkvOIiPQeMlU6Im1nvlsYqLUosgmRz1UQOXcwuO0vgqASl7DNg+w==", + "requires": { + "nan": "2.10.0" + } + }, "dom-converter": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.1.4.tgz", @@ -3642,6 +3687,16 @@ "escape-string-regexp": "1.0.5" } }, + "file-loader": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-1.1.11.tgz", + "integrity": "sha512-TGR4HU7HUsGg6GCOPJnFk06RhWgEWFLAGWiT6rcD+GRC2keU3s9RGJ+b3Z6/U73jwwNb2gKLJ7YCrp+jvU4ALg==", + "dev": true, + "requires": { + "loader-utils": "1.1.0", + "schema-utils": "0.4.5" + } + }, "filename-regex": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", @@ -6210,6 +6265,11 @@ "integrity": "sha512-H7ErYLM34CvDMto3GbD6xD0JLUGYXR3QTcH6B/tr4Hi/QpSThnCsIp+Sy5FRTw3B0d6py4HcNkW7nO/wdtGWEw==", "dev": true }, + "js-cookie": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.0.tgz", + "integrity": "sha1-Gywnmm7s44ChIWi5JIUmWzWx7/s=" + }, "js-tokens": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", @@ -6998,6 +7058,24 @@ "object-visit": "1.0.1" } }, + "markdown": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/markdown/-/markdown-0.5.0.tgz", + "integrity": "sha1-KCBbVlqK51kt4gdGPWY33BgnIrI=", + "requires": { + "nopt": "2.1.2" + }, + "dependencies": { + "nopt": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-2.1.2.tgz", + "integrity": "sha1-bMzZd7gBMqB3MdbozljCyDA8+a8=", + "requires": { + "abbrev": "1.1.1" + } + } + } + }, "math-clamp-x": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/math-clamp-x/-/math-clamp-x-1.2.0.tgz", @@ -7335,6 +7413,11 @@ "minimist": "0.0.8" } }, + "moment": { + "version": "2.22.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.22.1.tgz", + "integrity": "sha512-shJkRTSebXvsVqk56I+lkb2latjBs8I+pc2TzWc545y2iFnSjm7Wg0QMh+ZWcdSLQyGEau5jI8ocnmkyTgr9YQ==" + }, "move-concurrently": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", @@ -7381,8 +7464,7 @@ "nan": { "version": "2.10.0", "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz", - "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==", - "dev": true + "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==" }, "nan-x": { "version": "1.0.2", @@ -13491,6 +13573,15 @@ "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", "dev": true }, + "resolve-path": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/resolve-path/-/resolve-path-1.4.0.tgz", + "integrity": "sha1-xL2p9e+y/OZSR4c6s2u02DT+Fvc=", + "requires": { + "http-errors": "1.6.2", + "path-is-absolute": "1.0.1" + } + }, "resolve-url": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", @@ -17497,6 +17588,14 @@ "recast": "0.12.9", "temp": "0.8.3", "write-file-atomic": "1.3.4" + }, + "dependencies": { + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + } } }, "kind-of": { diff --git a/package.json b/package.json index 9bc0c9a..c4db06d 100644 --- a/package.json +++ b/package.json @@ -15,17 +15,24 @@ "author": "Mark van Renswoude ", "license": "Unlicense", "dependencies": { + "@fortawesome/fontawesome": "^1.1.5", + "@fortawesome/fontawesome-free-solid": "^5.0.10", + "@fortawesome/vue-fontawesome": "0.0.22", + "async": "^2.6.0", "async-retry": "^1.2.1", "bcrypt": "^1.0.3", "body-parser": "^1.18.2", "cookie-parser": "^1.4.3", "debug": "^3.1.0", + "diskusage": "^0.2.4", "express": "^4.16.3", "express-async-handler": "^1.1.2", "js-cookie": "^2.2.0", "jsonwebtoken": "^8.2.0", "lodash": "^4.17.5", + "markdown": "^0.5.0", "mkdirp": "^0.5.1", + "moment": "^2.22.1", "nedb": "^1.8.0", "npm": "^5.8.0", "resolve-path": "^1.4.0", @@ -37,6 +44,7 @@ "babel-loader": "^7.1.4", "babel-plugin-syntax-dynamic-import": "^6.18.0", "css-loader": "^0.28.11", + "file-loader": "^1.1.11", "html-webpack-plugin": "^3.1.0", "node-sass": "^4.8.3", "purecss": "^1.0.0", diff --git a/public/src/App.vue b/public/src/App.vue index 0ac5bb3..171f1a5 100644 --- a/public/src/App.vue +++ b/public/src/App.vue @@ -16,6 +16,7 @@ import _ from 'lodash'; import shared from './shared'; + export default { name: 'app', data() { @@ -65,10 +66,10 @@ export default { \ No newline at end of file diff --git a/public/src/app.js b/public/src/app.js index 17493af..44ece72 100644 --- a/public/src/app.js +++ b/public/src/app.js @@ -3,7 +3,11 @@ import VueI18n from 'vue-i18n'; import VueRouter from 'vue-router'; import App from './App.vue'; import messages from './lang'; - +import shared from './shared'; +import fontawesome from '@fortawesome/fontawesome'; +import FontAwesomeIcon from '@fortawesome/vue-fontawesome'; +import solid from '@fortawesome/fontawesome-free-solid'; +import moment from 'moment' if (typeof customMessages !== 'undefined') { @@ -20,6 +24,9 @@ if (typeof customMessages !== 'undefined') } +fontawesome.library.add(solid); +Vue.component('fa', FontAwesomeIcon); + Vue.use(VueI18n); Vue.use(VueRouter); @@ -30,7 +37,52 @@ const i18n = new VueI18n({ }); +Vue.filter('formatDateTime', (value) => +{ + if (value) + { + return moment(String(value)).format(i18n.t('dateTimeFormat')) + } +}); + + +// All credit goes to: https://stackoverflow.com/a/14919494 +function humanFileSize(bytes, si) +{ + var thresh = si ? 1000 : 1024; + if(Math.abs(bytes) < thresh) + { + return bytes + ' B'; + } + + var units = si + ? ['kB','MB','GB','TB','PB','EB','ZB','YB'] + : ['KiB','MiB','GiB','TiB','PiB','EiB','ZiB','YiB']; + var u = -1; + + do + { + bytes /= thresh; + ++u; + } + while(Math.abs(bytes) >= thresh && u < units.length - 1); + + return bytes.toFixed(1) + ' ' + units[u]; +} + +Vue.filter('formatSize', (value) => +{ + return humanFileSize(value, false); +}); + +Vue.filter('formatSizeSI', (value) => +{ + return humanFileSize(value, true); +}); + + const Code = () => import('./route/Code.vue'); +const AdminCodeDetail = () => import('./route/admin/CodeDetail.vue'); const router = new VueRouter({ @@ -45,6 +97,8 @@ const router = new VueRouter({ { path: '/admin', component: () => import('./route/admin/Landing.vue'), children: [ { path: 'uploads', component: () => import('./route/admin/Uploads.vue') }, + { path: 'codes/add', component: AdminCodeDetail }, + { path: 'codes/edit/:codeParam', component: AdminCodeDetail, props: true }, { path: 'codes', component: () => import('./route/admin/Codes.vue') }, { path: 'profile', component: () => import('./route/admin/Profile.vue') }, { path: 'users', component: () => import('./route/admin/Users.vue') }, @@ -55,6 +109,22 @@ const router = new VueRouter({ ] }); + +router.beforeEach((to, from, next) => +{ + if (to.path.startsWith('/admin/') && to.path != '/admin/') + { + if (!shared.adminToken) + { + router.push('/admin'); + return; + } + } + + next(); +}); + + new Vue({ el: '#app', i18n, diff --git a/public/src/locale/en.js b/public/src/locale/en.js index 4bc8fb8..0e45835 100644 --- a/public/src/locale/en.js +++ b/public/src/locale/en.js @@ -1,6 +1,7 @@ export default { title: 'File upload - Recv', disclaimer: '', + dateTimeFormat: 'MM/DD/YYYY hh:mm a', landing: { invitePlaceholder: 'Code', @@ -11,9 +12,12 @@ export default { }, notification: { - invalidCode: 'The specified code is invalid or has expired' + invalidCode: 'The specified code is invalid or has expired', + invalidLogin: 'The specified username or password is incorrect' }, + messageFrom: 'Message from ', + uppyDashboard: { done: 'Done', dropPaste: 'Drop files here, paste or', @@ -25,6 +29,10 @@ export default { loading: 'Loading…', empty: 'No data', logout: 'Logout', + cancel: 'Cancel', + save: 'Save', + + diskspace: '%{available} disk space available of %{total} total', login: { usernamePlaceholder: 'Username or e-mail address', @@ -41,14 +49,28 @@ export default { uploads: { created: 'Date', + code: 'Code', owner: 'Owner' }, codes: { - code: 'Code', - owner: 'Owner', + add: 'Generate code', - add: 'Generate code' + list: { + code: 'Code', + owner: 'Owner' + }, + + detail: { + code: 'Code', + owner: 'Owner', + created: 'Date created', + description: 'Description', + descriptionHint: 'The description will be visible only in the admin panel', + expiration: 'Expiration', + message: 'Message', + messageHint: 'The message will be displayed to the user on the upload page after the code is entered. Markdown is supported.' + } } } } \ No newline at end of file diff --git a/public/src/locale/nl.js b/public/src/locale/nl.js index 9fef1c1..321886c 100644 --- a/public/src/locale/nl.js +++ b/public/src/locale/nl.js @@ -1,6 +1,7 @@ export default { title: 'Bestandsoverdracht - Recv', disclaimer: '', + dateTimeFormat: 'DD-MM-YYYY HH:mm', landing: { invitePlaceholder: 'Code', @@ -11,9 +12,12 @@ export default { }, notification: { - invalidCode: 'De ingevoerde code is ongeldig of verlopen' + invalidCode: 'De ingevoerde code is ongeldig of verlopen', + invalidLogin: 'De ingevoerde gebruikersnaam of wachtwoord is incorrect' }, + messageFrom: 'Bericht van ', + uppyDashboard: { done: 'Gereed', dropPaste: 'Sleep bestanden, plak of ', @@ -25,6 +29,10 @@ export default { loading: 'Bezig met laden…', empty: 'Geen gegevens', logout: 'Uitloggen', + cancel: 'Annuleren', + save: 'Opslaan', + + diskspace: '%{available} schijfruimte beschikbaar van %{total} totaal', login: { usernamePlaceholder: 'Gebruikersnaam of e-mail adres', @@ -41,14 +49,28 @@ export default { uploads: { created: 'Datum', + code: 'Code', owner: 'Eigenaar' }, codes: { - code: 'Code', - owner: 'Eigenaar', + add: 'Genereer code', - add: 'Genereer code' + list: { + code: 'Code', + owner: 'Eigenaar' + }, + + detail: { + code: 'Code', + owner: 'Eigenaar', + created: 'Datum aangemaakt', + description: 'Omschrijving', + descriptionHint: 'De omschrijving is alleen zichtbaar in beheer', + expiration: 'Verloopt', + message: 'Bericht', + messageHint: 'Het bericht wordt getoond aan de gebruiker op de upload pagina na het invoeren van de code. Markdown kan worden gebruikt.' + } } } } \ No newline at end of file diff --git a/public/src/route/Code.vue b/public/src/route/Code.vue index 5ff3ff6..65b02a5 100644 --- a/public/src/route/Code.vue +++ b/public/src/route/Code.vue @@ -52,7 +52,7 @@ export default { }) .then((response) => { - shared.token = response.data; + shared.uploadToken = response.data; self.$router.push({ path: '/u/' + self.code.value }); }) .catch((error) => @@ -72,7 +72,7 @@ export default { \ No newline at end of file diff --git a/public/src/route/admin/CodeDetail.vue b/public/src/route/admin/CodeDetail.vue new file mode 100644 index 0000000..4a29b38 --- /dev/null +++ b/public/src/route/admin/CodeDetail.vue @@ -0,0 +1,127 @@ + + + + + \ No newline at end of file diff --git a/public/src/route/admin/Codes.vue b/public/src/route/admin/Codes.vue index b2ee459..473ea76 100644 --- a/public/src/route/admin/Codes.vue +++ b/public/src/route/admin/Codes.vue @@ -1,38 +1,34 @@ \ No newline at end of file diff --git a/public/src/route/admin/Landing.vue b/public/src/route/admin/Landing.vue index 3be8568..1313961 100644 --- a/public/src/route/admin/Landing.vue +++ b/public/src/route/admin/Landing.vue @@ -1,18 +1,113 @@ \ No newline at end of file diff --git a/public/src/route/admin/Login.vue b/public/src/route/admin/Login.vue index e186f7b..9a0c16b 100644 --- a/public/src/route/admin/Login.vue +++ b/public/src/route/admin/Login.vue @@ -12,7 +12,7 @@ - + @@ -48,13 +48,13 @@ export default { }) .then((response) => { - shared.token = response.data; + shared.adminToken = response.data; self.$router.push({ path: '/admin/uploads' }); }) .catch((error) => { if (error.response && error.response.status == 403) - shared.$emit('showNotification', self.$i18n.t('notification.invalidCode'), false); + shared.$emit('showNotification', self.$i18n.t('notification.invalidLogin'), false); else shared.$emit('showNotification', error.message); }) diff --git a/public/src/route/admin/Menu.vue b/public/src/route/admin/Menu.vue index 275b05e..a7467fc 100644 --- a/public/src/route/admin/Menu.vue +++ b/public/src/route/admin/Menu.vue @@ -41,23 +41,21 @@ export default { methods: { hasAuth(token) { - return shared.user.auth.indexOf(token) > -1; + return shared.user !== null && shared.user.auth.indexOf(token) > -1; }, logout() { var self = this; - - shared.token = null; - self.$router.push('/admin'); + shared.adminToken = null; } } } \ No newline at end of file diff --git a/public/src/route/admin/Users.vue b/public/src/route/admin/Users.vue index 37c44fa..f4e9c2c 100644 --- a/public/src/route/admin/Users.vue +++ b/public/src/route/admin/Users.vue @@ -1,25 +1,15 @@ \ No newline at end of file diff --git a/public/src/shared.js b/public/src/shared.js index 055a91b..36c38b2 100644 --- a/public/src/shared.js +++ b/public/src/shared.js @@ -6,8 +6,9 @@ import axios from 'axios'; export default new Vue({ data() { return { - token: null, - user: [] + uploadToken: null, + adminToken: null, + user: null } }, @@ -15,32 +16,47 @@ export default new Vue({ { var self = this; - var cookie = Cookies.get('token'); + var cookie = Cookies.get('adminToken'); if (typeof cookie !== 'undefined') - self.token = cookie; + self.adminToken = cookie; + + + self.$on('apiError', (error, $router) => + { + if (error.response && error.response.status == 403) + { + self.adminToken = null; + $router.push('/admin'); + } + else + self.$emit('showNotification', error.message); + }); }, watch: { - token(newValue) + adminToken(newValue) { var self = this; if (newValue !== null) { - Cookies.set('token', newValue); + Cookies.set('adminToken', newValue); axios.get('/admin/whoami', { headers: { - Authorization: 'Bearer ' + self.token + Authorization: 'Bearer ' + self.adminToken }}) .then((response) => { self.user = response.data; + }) + .catch((error) => + { }); } else { - Cookies.remove('token'); + Cookies.remove('adminToken'); self.user = null; } }