Changed to nanoid for more flexible code generation
Added deleting of uploads Adding a new code now goes into edit mode after applying, so you can copy the code directly
This commit is contained in:
parent
45ae38bd66
commit
6f827382ff
|
@ -15,5 +15,12 @@ module.exports = {
|
||||||
url: '/files'
|
url: '/files'
|
||||||
},
|
},
|
||||||
|
|
||||||
jwtSecret: 'change me to a random generated string'
|
jwtSecret: 'change me to a random generated string',
|
||||||
|
|
||||||
|
code: {
|
||||||
|
// Use https://alex7kom.github.io/nano-nanoid-cc/ to check for the chance of collisions.
|
||||||
|
// If a collision occurs, a new code will be generated and tested again for up to 100 times.
|
||||||
|
alphabet: '1234567890abcdef',
|
||||||
|
length: 8
|
||||||
|
}
|
||||||
};
|
};
|
|
@ -6,6 +6,7 @@ const path = require('path');
|
||||||
const resolvePath = require('resolve-path');
|
const resolvePath = require('resolve-path');
|
||||||
const AuthTokens = require('../authtokens');
|
const AuthTokens = require('../authtokens');
|
||||||
const disk = require('diskusage');
|
const disk = require('diskusage');
|
||||||
|
const fs = require('mz/fs');
|
||||||
|
|
||||||
|
|
||||||
async function checkAuthorization(req, res, repository, onVerified)
|
async function checkAuthorization(req, res, repository, onVerified)
|
||||||
|
@ -111,7 +112,7 @@ module.exports = (repository) =>
|
||||||
await checkAuthorization(req, res, repository, async (user) =>
|
await checkAuthorization(req, res, repository, async (user) =>
|
||||||
{
|
{
|
||||||
var codes = await repository.codes.getCodes(user.hasAuth(AuthTokens.ViewAllCodes) ? null : user.userId);
|
var codes = await repository.codes.getCodes(user.hasAuth(AuthTokens.ViewAllCodes) ? null : user.userId);
|
||||||
var usernames = await repository.users.getUserNames();
|
var usernames = await repository.users.getNames();
|
||||||
|
|
||||||
codes.forEach((item) =>
|
codes.forEach((item) =>
|
||||||
{
|
{
|
||||||
|
@ -190,11 +191,13 @@ module.exports = (repository) =>
|
||||||
await checkAuthorization(req, res, repository, async (user) =>
|
await checkAuthorization(req, res, repository, async (user) =>
|
||||||
{
|
{
|
||||||
var files = await repository.uploads.getUploads(user.hasAuth(AuthTokens.ViewAllUploads) ? null : user.userId);
|
var files = await repository.uploads.getUploads(user.hasAuth(AuthTokens.ViewAllUploads) ? null : user.userId);
|
||||||
var usernames = await repository.users.getUserNames();
|
var usernames = await repository.users.getNames();
|
||||||
|
var codedescriptions = await repository.codes.getDescriptions();
|
||||||
|
|
||||||
files.forEach((item) =>
|
files.forEach((item) =>
|
||||||
{
|
{
|
||||||
item.username = usernames[item.userId];
|
item.username = item.userId !== null ? usernames[item.userId] : null;
|
||||||
|
item.codedescription = item.code !== null ? codedescriptions[item.code] : null;
|
||||||
});
|
});
|
||||||
|
|
||||||
res.send(files);
|
res.send(files);
|
||||||
|
@ -202,6 +205,41 @@ module.exports = (repository) =>
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
router.delete('/uploads/:id', asyncHandler(async (req, res) =>
|
||||||
|
{
|
||||||
|
await checkAuthorization(req, res, repository, async (user) =>
|
||||||
|
{
|
||||||
|
var upload = await repository.uploads.getUpload(req.params.id);
|
||||||
|
if (upload == null || (upload.userId !== user.userId && !user.hasAuth(AuthTokens.ViewAllUploads)))
|
||||||
|
{
|
||||||
|
res.sendStatus(404);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
repository.uploads.delete(upload.id);
|
||||||
|
|
||||||
|
await Promise.all(upload.files.map(async (file) =>
|
||||||
|
{
|
||||||
|
if (!file.id) return;
|
||||||
|
if (!(await repository.uploads.fileExists(file.id)))
|
||||||
|
{
|
||||||
|
var fullpath = resolvePath(config.fileUpload.path, file.id);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await fs.unlink(fullpath);
|
||||||
|
}
|
||||||
|
catch (err)
|
||||||
|
{
|
||||||
|
console.log('Failed to delete ' + fullpath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
res.sendStatus(200);
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
router.get('/download/:fileid/:displayname', asyncHandler(async (req, res) =>
|
router.get('/download/:fileid/:displayname', asyncHandler(async (req, res) =>
|
||||||
{
|
{
|
||||||
await checkAuthorization(req, res, repository, async (user) =>
|
await checkAuthorization(req, res, repository, async (user) =>
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const retry = require('async-retry');
|
const retry = require('async-retry');
|
||||||
const shortid = require('shortid');
|
const generate = require('nanoid/generate');
|
||||||
const markdown = require('markdown').markdown;
|
const markdown = require('markdown').markdown;
|
||||||
|
const config = require('../../config');
|
||||||
|
|
||||||
shortid.characters('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-.');
|
|
||||||
|
|
||||||
|
|
||||||
class Code
|
class Code
|
||||||
|
@ -70,7 +68,7 @@ class CodeRepository
|
||||||
|
|
||||||
return await retry(async bail =>
|
return await retry(async bail =>
|
||||||
{
|
{
|
||||||
var codeId = shortid.generate();
|
var codeId = generate(config.code.alphabet, config.code.length);
|
||||||
|
|
||||||
if ((await self.findCodeUserId(codeId)) !== null)
|
if ((await self.findCodeUserId(codeId)) !== null)
|
||||||
throw new Error('Code ' + codeId + ' already exists');
|
throw new Error('Code ' + codeId + ' already exists');
|
||||||
|
@ -145,6 +143,33 @@ class CodeRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
getDescriptions()
|
||||||
|
{
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) =>
|
||||||
|
{
|
||||||
|
self.store.find({}, (err, docs) =>
|
||||||
|
{
|
||||||
|
if (err)
|
||||||
|
{
|
||||||
|
reject(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var descriptions = {};
|
||||||
|
docs.forEach((dbCode) =>
|
||||||
|
{
|
||||||
|
if (dbCode.description)
|
||||||
|
descriptions[dbCode._id] = dbCode.description;
|
||||||
|
});
|
||||||
|
|
||||||
|
resolve(descriptions);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
getCode(codeId)
|
getCode(codeId)
|
||||||
{
|
{
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
|
@ -9,6 +9,7 @@ class Upload
|
||||||
|
|
||||||
self.id = values.id || values._id || null;
|
self.id = values.id || values._id || null;
|
||||||
self.userId = values.userId || null;
|
self.userId = values.userId || null;
|
||||||
|
self.code = values.code || null;
|
||||||
self.created = values.created || new Date();
|
self.created = values.created || new Date();
|
||||||
self.expiration = values.expiration || null;
|
self.expiration = values.expiration || null;
|
||||||
self.files = values.files || [];
|
self.files = values.files || [];
|
||||||
|
@ -107,6 +108,66 @@ class UploadRepository
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
getUpload(uploadId)
|
||||||
|
{
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) =>
|
||||||
|
{
|
||||||
|
self.store.findOne({ _id: uploadId }, (err, doc) =>
|
||||||
|
{
|
||||||
|
if (err)
|
||||||
|
{
|
||||||
|
reject(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(doc !== null ? new Upload(doc) : null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
delete(uploadId)
|
||||||
|
{
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) =>
|
||||||
|
{
|
||||||
|
self.store.remove({ _id: uploadId }, (err, numRemoved) =>
|
||||||
|
{
|
||||||
|
if (err)
|
||||||
|
{
|
||||||
|
reject(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fileExists(fileId)
|
||||||
|
{
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) =>
|
||||||
|
{
|
||||||
|
self.store.findOne({ 'files.id': fileId }, (err, doc) =>
|
||||||
|
{
|
||||||
|
if (err)
|
||||||
|
{
|
||||||
|
reject(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(doc !== null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -165,7 +165,7 @@ class UserRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
getUserNames()
|
getNames()
|
||||||
{
|
{
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
|
|
|
@ -184,6 +184,11 @@
|
||||||
"integrity": "sha1-xnhwBYADV5AJCD9UrAq6+1wz0kI=",
|
"integrity": "sha1-xnhwBYADV5AJCD9UrAq6+1wz0kI=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"any-promise": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
|
||||||
|
"integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8="
|
||||||
|
},
|
||||||
"anymatch": {
|
"anymatch": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz",
|
||||||
|
@ -7455,6 +7460,16 @@
|
||||||
"integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=",
|
"integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"mz": {
|
||||||
|
"version": "2.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
|
||||||
|
"integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
|
||||||
|
"requires": {
|
||||||
|
"any-promise": "1.3.0",
|
||||||
|
"object-assign": "4.1.1",
|
||||||
|
"thenify-all": "1.6.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"namespace-emitter": {
|
"namespace-emitter": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/namespace-emitter/-/namespace-emitter-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/namespace-emitter/-/namespace-emitter-2.0.1.tgz",
|
||||||
|
@ -7472,6 +7487,11 @@
|
||||||
"integrity": "sha512-dndRmy03JQEN+Nh6WjQl7/OstIozeEmrtWe4TE7mEqJ8W8oMD8m2tHjsLPWt//e3hLAeRSbs4pxMyc5pk/nCkQ==",
|
"integrity": "sha512-dndRmy03JQEN+Nh6WjQl7/OstIozeEmrtWe4TE7mEqJ8W8oMD8m2tHjsLPWt//e3hLAeRSbs4pxMyc5pk/nCkQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"nanoid": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-sCTwJt690lduNHyqknXJp8pRwzm80neOLGaiTHU2KUJZFVSErl778NNCIivEQCX5gNT0xR1Jy3HEMe/TABT6lw=="
|
||||||
|
},
|
||||||
"nanomatch": {
|
"nanomatch": {
|
||||||
"version": "1.2.9",
|
"version": "1.2.9",
|
||||||
"resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.9.tgz",
|
"resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.9.tgz",
|
||||||
|
@ -13912,11 +13932,6 @@
|
||||||
"integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=",
|
"integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"shortid": {
|
|
||||||
"version": "2.2.8",
|
|
||||||
"resolved": "https://registry.npmjs.org/shortid/-/shortid-2.2.8.tgz",
|
|
||||||
"integrity": "sha1-AzsRfWoul1gE9vCWnb59PQs1UTE="
|
|
||||||
},
|
|
||||||
"signal-exit": {
|
"signal-exit": {
|
||||||
"version": "3.0.2",
|
"version": "3.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
|
||||||
|
@ -14605,6 +14620,22 @@
|
||||||
"integrity": "sha512-j5EMxnryTvKxwH2Cq+Pb43tsf6sdEgw6Pdwxk83mPaq0ToeFJt6WE4J3s5BqY7vmjlLgkgXvhtXUxo80FyBhCA==",
|
"integrity": "sha512-j5EMxnryTvKxwH2Cq+Pb43tsf6sdEgw6Pdwxk83mPaq0ToeFJt6WE4J3s5BqY7vmjlLgkgXvhtXUxo80FyBhCA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"thenify": {
|
||||||
|
"version": "3.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.0.tgz",
|
||||||
|
"integrity": "sha1-5p44obq+lpsBCCB5eLn2K4hgSDk=",
|
||||||
|
"requires": {
|
||||||
|
"any-promise": "1.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"thenify-all": {
|
||||||
|
"version": "1.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
|
||||||
|
"integrity": "sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=",
|
||||||
|
"requires": {
|
||||||
|
"thenify": "3.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"through": {
|
"through": {
|
||||||
"version": "2.3.8",
|
"version": "2.3.8",
|
||||||
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
|
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
|
||||||
|
|
|
@ -33,10 +33,11 @@
|
||||||
"markdown": "^0.5.0",
|
"markdown": "^0.5.0",
|
||||||
"mkdirp": "^0.5.1",
|
"mkdirp": "^0.5.1",
|
||||||
"moment": "^2.22.1",
|
"moment": "^2.22.1",
|
||||||
|
"mz": "^2.7.0",
|
||||||
|
"nanoid": "^1.0.2",
|
||||||
"nedb": "^1.8.0",
|
"nedb": "^1.8.0",
|
||||||
"npm": "^5.8.0",
|
"npm": "^5.8.0",
|
||||||
"resolve-path": "^1.4.0",
|
"resolve-path": "^1.4.0",
|
||||||
"shortid": "^2.2.8",
|
|
||||||
"tus-node-server": "^0.2.10"
|
"tus-node-server": "^0.2.10"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
@ -93,6 +93,16 @@ body
|
||||||
font-size: 12pt;
|
font-size: 12pt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a, a:hover
|
||||||
|
{
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a
|
||||||
|
{
|
||||||
|
color: #228dd4;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
pure-g uses hardcoded sans-serif to get accurate alignment, this class
|
pure-g uses hardcoded sans-serif to get accurate alignment, this class
|
||||||
is added as a workaround for text in rows
|
is added as a workaround for text in rows
|
||||||
|
|
|
@ -96,13 +96,13 @@ const router = new VueRouter({
|
||||||
},
|
},
|
||||||
{ path: '/admin', component: () => import('./route/admin/Landing.vue'),
|
{ path: '/admin', component: () => import('./route/admin/Landing.vue'),
|
||||||
children: [
|
children: [
|
||||||
{ path: 'uploads', component: () => import('./route/admin/Uploads.vue') },
|
{ path: 'uploads', name: 'adminDefault', component: () => import('./route/admin/Uploads.vue') },
|
||||||
{ path: 'codes/add', component: AdminCodeDetail },
|
{ path: 'codes/add', component: AdminCodeDetail },
|
||||||
{ path: 'codes/edit/:codeParam', component: AdminCodeDetail, props: true },
|
{ path: 'codes/edit/:codeParam', component: AdminCodeDetail, props: true },
|
||||||
{ path: 'codes', component: () => import('./route/admin/Codes.vue') },
|
{ path: 'codes', component: () => import('./route/admin/Codes.vue') },
|
||||||
{ path: 'profile', component: () => import('./route/admin/Profile.vue') },
|
{ path: 'profile', component: () => import('./route/admin/Profile.vue') },
|
||||||
{ path: 'users', component: () => import('./route/admin/Users.vue') },
|
{ path: 'users', component: () => import('./route/admin/Users.vue') },
|
||||||
{ path: '', component: () => import('./route/admin/Login.vue') }
|
{ path: '', name: 'adminRoot', component: () => import('./route/admin/Login.vue') }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{ path: '*', redirect: '/' }
|
{ path: '*', redirect: '/' }
|
||||||
|
@ -112,14 +112,21 @@ const router = new VueRouter({
|
||||||
|
|
||||||
router.beforeEach((to, from, next) =>
|
router.beforeEach((to, from, next) =>
|
||||||
{
|
{
|
||||||
if (to.path.startsWith('/admin/') && to.path != '/admin/')
|
let isAdminRoot = to.name == 'adminRoot';
|
||||||
|
|
||||||
|
if (to.path.startsWith('/admin/') && !isAdminRoot)
|
||||||
{
|
{
|
||||||
if (!shared.adminToken)
|
if (!shared.adminToken)
|
||||||
{
|
{
|
||||||
router.push('/admin');
|
router.push({ name: 'adminRoot' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (isAdminRoot && shared.adminToken)
|
||||||
|
{
|
||||||
|
router.push({ name: 'adminDefault' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
|
|
|
@ -31,6 +31,7 @@ export default {
|
||||||
logout: 'Logout',
|
logout: 'Logout',
|
||||||
cancel: 'Cancel',
|
cancel: 'Cancel',
|
||||||
save: 'Save',
|
save: 'Save',
|
||||||
|
delete: 'Delete',
|
||||||
|
|
||||||
diskspace: '%{available} disk space available of %{total} total',
|
diskspace: '%{available} disk space available of %{total} total',
|
||||||
|
|
||||||
|
@ -63,6 +64,7 @@ export default {
|
||||||
|
|
||||||
detail: {
|
detail: {
|
||||||
code: 'Code',
|
code: 'Code',
|
||||||
|
codeHint: 'Direct link to the upload page for this code:',
|
||||||
owner: 'Owner',
|
owner: 'Owner',
|
||||||
created: 'Date created',
|
created: 'Date created',
|
||||||
description: 'Description',
|
description: 'Description',
|
||||||
|
|
|
@ -31,6 +31,7 @@ export default {
|
||||||
logout: 'Uitloggen',
|
logout: 'Uitloggen',
|
||||||
cancel: 'Annuleren',
|
cancel: 'Annuleren',
|
||||||
save: 'Opslaan',
|
save: 'Opslaan',
|
||||||
|
delete: 'Verwijderen',
|
||||||
|
|
||||||
diskspace: '%{available} schijfruimte beschikbaar van %{total} totaal',
|
diskspace: '%{available} schijfruimte beschikbaar van %{total} totaal',
|
||||||
|
|
||||||
|
@ -63,6 +64,7 @@ export default {
|
||||||
|
|
||||||
detail: {
|
detail: {
|
||||||
code: 'Code',
|
code: 'Code',
|
||||||
|
codeHint: 'Directe link naar de upload pagina voor deze code:',
|
||||||
owner: 'Eigenaar',
|
owner: 'Eigenaar',
|
||||||
created: 'Datum aangemaakt',
|
created: 'Datum aangemaakt',
|
||||||
description: 'Omschrijving',
|
description: 'Omschrijving',
|
||||||
|
|
|
@ -7,6 +7,10 @@
|
||||||
<label for="codeId">{{ $t('admin.codes.detail.code') }}</label>
|
<label for="codeId">{{ $t('admin.codes.detail.code') }}</label>
|
||||||
<input id="codeId" type="text" readonly :value="code.id" class="pure-input-2-3">
|
<input id="codeId" type="text" readonly :value="code.id" class="pure-input-2-3">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="pure-form-description" v-if="code.id">
|
||||||
|
<div>{{ $t('admin.codes.detail.codeHint') }}</div>
|
||||||
|
<a :href="getCodeUrl(code.id)" target="_blank">{{ getCodeUrl(code.id) }}</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group" v-if="code.id">
|
<div class="pure-control-group" v-if="code.id">
|
||||||
<label for="user">{{ $t('admin.codes.detail.owner') }}</label>
|
<label for="user">{{ $t('admin.codes.detail.owner') }}</label>
|
||||||
|
@ -23,7 +27,7 @@
|
||||||
<input id="description" type="text" v-model="code.description" class="pure-input-2-3">
|
<input id="description" type="text" v-model="code.description" class="pure-input-2-3">
|
||||||
</div>
|
</div>
|
||||||
<div class="pure-form-description">
|
<div class="pure-form-description">
|
||||||
{{ $t('admin.codes.detail.descriptionHint' )}}
|
{{ $t('admin.codes.detail.descriptionHint') }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
|
@ -38,7 +42,7 @@
|
||||||
<textarea id="message" type="text" v-model="code.message" class="pure-input-2-3" rows="10"></textarea>
|
<textarea id="message" type="text" v-model="code.message" class="pure-input-2-3" rows="10"></textarea>
|
||||||
</div>
|
</div>
|
||||||
<div class="pure-form-description">
|
<div class="pure-form-description">
|
||||||
{{ $t('admin.codes.detail.messageHint' )}}
|
{{ $t('admin.codes.detail.messageHint') }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-controls">
|
<div class="pure-controls">
|
||||||
|
@ -66,33 +70,62 @@ export default {
|
||||||
|
|
||||||
props: ['codeParam'],
|
props: ['codeParam'],
|
||||||
|
|
||||||
created()
|
|
||||||
{
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
if (self.codeParam)
|
watch: {
|
||||||
|
'$route' (to, from)
|
||||||
{
|
{
|
||||||
axios.get('/admin/codes/' + encodeURIComponent(self.codeParam), {
|
var self = this;
|
||||||
headers: {
|
self.checkCode();
|
||||||
Authorization: 'Bearer ' + shared.adminToken
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then((response) =>
|
|
||||||
{
|
|
||||||
self.code = response.data;
|
|
||||||
})
|
|
||||||
.catch((error) => { shared.$emit('apiError', error, this.$router) });
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
self.code = {
|
|
||||||
expiration: null,
|
|
||||||
message: null
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
created()
|
||||||
|
{
|
||||||
|
var self = this;
|
||||||
|
self.checkCode();
|
||||||
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
checkCode()
|
||||||
|
{
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if (self.codeParam)
|
||||||
|
{
|
||||||
|
axios.get('/admin/codes/' + encodeURIComponent(self.codeParam), {
|
||||||
|
headers: {
|
||||||
|
Authorization: 'Bearer ' + shared.adminToken
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then((response) =>
|
||||||
|
{
|
||||||
|
self.code = response.data;
|
||||||
|
})
|
||||||
|
.catch((error) => { shared.$emit('apiError', error, this.$router) });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
self.code = {
|
||||||
|
expiration: null,
|
||||||
|
message: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getCodeUrl(code)
|
||||||
|
{
|
||||||
|
var port = ':' + window.location.port;
|
||||||
|
|
||||||
|
if ((window.location.protocol == 'http:' && window.location.port == 80) ||
|
||||||
|
(window.location.protocol == 'https:' && window.location.port == 443))
|
||||||
|
port = '';
|
||||||
|
|
||||||
|
return window.location.protocol + '//' +
|
||||||
|
window.location.hostname + port + '/c/' +
|
||||||
|
encodeURIComponent(code);
|
||||||
|
},
|
||||||
|
|
||||||
save()
|
save()
|
||||||
{
|
{
|
||||||
var self = this;
|
var self = this;
|
||||||
|
@ -111,7 +144,10 @@ export default {
|
||||||
})
|
})
|
||||||
.then((response) =>
|
.then((response) =>
|
||||||
{
|
{
|
||||||
this.$router.push('/admin/codes');
|
if (self.code.id)
|
||||||
|
this.$router.push('/admin/codes');
|
||||||
|
else
|
||||||
|
this.$router.push('/admin/codes/edit/' + response.data);
|
||||||
})
|
})
|
||||||
.catch((error) => { shared.$emit('apiError', error, this.$router) })
|
.catch((error) => { shared.$emit('apiError', error, this.$router) })
|
||||||
.then(() =>
|
.then(() =>
|
||||||
|
@ -121,7 +157,4 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
</style>
|
|
|
@ -55,12 +55,4 @@ export default {
|
||||||
.catch((error) => { shared.$emit('apiError', error, this.$router) });
|
.catch((error) => { shared.$emit('apiError', error, this.$router) });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
.description
|
|
||||||
{
|
|
||||||
font-size: 75%;
|
|
||||||
color: #808080;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -187,4 +187,9 @@ $list-padding: .2rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.description
|
||||||
|
{
|
||||||
|
font-size: 75%;
|
||||||
|
color: #808080;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
|
@ -49,7 +49,7 @@ export default {
|
||||||
.then((response) =>
|
.then((response) =>
|
||||||
{
|
{
|
||||||
shared.adminToken = response.data;
|
shared.adminToken = response.data;
|
||||||
self.$router.push({ path: '/admin/uploads' });
|
self.$router.push({ name: 'adminDefault' });
|
||||||
})
|
})
|
||||||
.catch((error) =>
|
.catch((error) =>
|
||||||
{
|
{
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
<menu-link route="/admin/users" :title="$t('admin.menu.users')" v-if="hasAuth('manageUsers')"></menu-link>
|
<menu-link route="/admin/users" :title="$t('admin.menu.users')" v-if="hasAuth('manageUsers')"></menu-link>
|
||||||
|
|
||||||
<li class="pure-menu-item right">
|
<li class="pure-menu-item right">
|
||||||
<a class="pure-menu-link" href="#" @click="logout">{{ $t('admin.logout') }}</a>
|
<a class="pure-menu-link" href="#" @click.prevent="logout">{{ $t('admin.logout') }}</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,21 +1,23 @@
|
||||||
<template>
|
<template>
|
||||||
<div id="uploads">
|
<div id="uploads">
|
||||||
<div v-if="uploads !== null">
|
<div v-if="uploads !== null">
|
||||||
<div class="pure-g list-header">
|
|
||||||
<div class="pure-u-1-3"><span class="text">{{ $t('admin.uploads.created') }}</span></div>
|
|
||||||
<div class="pure-u-1-3"><span class="text">{{ $t('admin.uploads.code') }}</span></div>
|
|
||||||
<div class="pure-u-1-3"><span class="text">{{ $t('admin.uploads.owner') }}</span></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-for="upload in uploads" class="list">
|
<div v-for="upload in uploads" class="list">
|
||||||
<div class="properties">
|
<div class="properties">
|
||||||
<div class="pure-g">
|
<div class="pure-g">
|
||||||
<div class="pure-u-1-3"><span class="text">{{ upload.created | formatDateTime }}</span></div>
|
<div class="pure-u-1-1"><span class="text codedescription">{{ upload.codedescription || upload.code }}</span></div>
|
||||||
<div class="pure-u-1-3"><span class="text">{{ upload.code }}</span></div>
|
<div class="pure-u-1-3"><span class="text">{{ upload.code }}</span></div>
|
||||||
<div class="pure-u-1-3"><span class="text">{{ upload.username }}</span></div>
|
<div class="pure-u-1-3"><span class="text">{{ upload.username }}</span></div>
|
||||||
|
<div class="pure-u-1-3 right"><span class="text">{{ upload.created | formatDateTime }}</span></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="pure-menu pure-menu-horizontal">
|
||||||
|
<ul class="pure-menu-list">
|
||||||
|
<li class="pure-menu-item" v-if="confirmDelete == upload.id"><a href="#" class="pure-menu-link" @click.prevent="cancelDelete"><fa icon="ban"></fa> {{ $t('admin.cancel') }}</a></li>
|
||||||
|
<li class="pure-menu-item"><a href="#" class="pure-menu-link" :class="{ confirm: confirmDelete == upload.id }" @click.prevent="deleteClick(upload.id)"><fa icon="trash-alt"></fa> {{ $t('admin.delete') }}</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="file" v-for="file in upload.files" :title="file.name">
|
<div class="file" v-for="file in upload.files" :title="file.name">
|
||||||
<a :href="getDownloadUrl(file)">
|
<a :href="getDownloadUrl(file)">
|
||||||
<img :src="getFileIconUrl(file.name)" class="icon">
|
<img :src="getFileIconUrl(file.name)" class="icon">
|
||||||
|
@ -45,7 +47,8 @@ export default {
|
||||||
data()
|
data()
|
||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
uploads: null
|
uploads: null,
|
||||||
|
confirmDelete: null
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -93,6 +96,39 @@ export default {
|
||||||
getDownloadUrl(file)
|
getDownloadUrl(file)
|
||||||
{
|
{
|
||||||
return '/admin/download/' + encodeURIComponent(file.id) + '/' + encodeURIComponent(file.name);
|
return '/admin/download/' + encodeURIComponent(file.id) + '/' + encodeURIComponent(file.name);
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
deleteClick(uploadId)
|
||||||
|
{
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if (self.confirmDelete == uploadId)
|
||||||
|
{
|
||||||
|
self.confirmDelete = null;
|
||||||
|
|
||||||
|
axios.delete('/admin/uploads/' + encodeURIComponent(uploadId), {
|
||||||
|
headers: {
|
||||||
|
Authorization: 'Bearer ' + shared.adminToken
|
||||||
|
}})
|
||||||
|
.then((response) =>
|
||||||
|
{
|
||||||
|
var index = _.findIndex(self.uploads, (item) => { return item.id == uploadId; });
|
||||||
|
if (index > -1)
|
||||||
|
self.uploads.splice(index, 1);
|
||||||
|
})
|
||||||
|
.catch((error) => { shared.$emit('apiError', error, this.$router) });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
self.confirmDelete = uploadId;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
cancelDelete()
|
||||||
|
{
|
||||||
|
var self = this;
|
||||||
|
self.confirmDelete = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -154,4 +190,22 @@ export default {
|
||||||
background-color: #f4f4f4;
|
background-color: #f4f4f4;
|
||||||
padding: .2rem;
|
padding: .2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.codedescription
|
||||||
|
{
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.right
|
||||||
|
{
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.confirm
|
||||||
|
{
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
Loading…
Reference in New Issue