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'
|
||||
},
|
||||
|
||||
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 AuthTokens = require('../authtokens');
|
||||
const disk = require('diskusage');
|
||||
const fs = require('mz/fs');
|
||||
|
||||
|
||||
async function checkAuthorization(req, res, repository, onVerified)
|
||||
|
@ -111,7 +112,7 @@ module.exports = (repository) =>
|
|||
await checkAuthorization(req, res, repository, async (user) =>
|
||||
{
|
||||
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) =>
|
||||
{
|
||||
|
@ -190,11 +191,13 @@ module.exports = (repository) =>
|
|||
await checkAuthorization(req, res, repository, async (user) =>
|
||||
{
|
||||
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) =>
|
||||
{
|
||||
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);
|
||||
|
@ -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) =>
|
||||
{
|
||||
await checkAuthorization(req, res, repository, async (user) =>
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
const _ = require('lodash');
|
||||
const retry = require('async-retry');
|
||||
const shortid = require('shortid');
|
||||
const generate = require('nanoid/generate');
|
||||
const markdown = require('markdown').markdown;
|
||||
|
||||
|
||||
shortid.characters('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-.');
|
||||
const config = require('../../config');
|
||||
|
||||
|
||||
class Code
|
||||
|
@ -70,7 +68,7 @@ class CodeRepository
|
|||
|
||||
return await retry(async bail =>
|
||||
{
|
||||
var codeId = shortid.generate();
|
||||
var codeId = generate(config.code.alphabet, config.code.length);
|
||||
|
||||
if ((await self.findCodeUserId(codeId)) !== null)
|
||||
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)
|
||||
{
|
||||
var self = this;
|
||||
|
|
|
@ -9,6 +9,7 @@ class Upload
|
|||
|
||||
self.id = values.id || values._id || null;
|
||||
self.userId = values.userId || null;
|
||||
self.code = values.code || null;
|
||||
self.created = values.created || new Date();
|
||||
self.expiration = values.expiration || null;
|
||||
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;
|
||||
|
||||
|
|
|
@ -184,6 +184,11 @@
|
|||
"integrity": "sha1-xnhwBYADV5AJCD9UrAq6+1wz0kI=",
|
||||
"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": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz",
|
||||
|
@ -7455,6 +7460,16 @@
|
|||
"integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=",
|
||||
"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": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/namespace-emitter/-/namespace-emitter-2.0.1.tgz",
|
||||
|
@ -7472,6 +7487,11 @@
|
|||
"integrity": "sha512-dndRmy03JQEN+Nh6WjQl7/OstIozeEmrtWe4TE7mEqJ8W8oMD8m2tHjsLPWt//e3hLAeRSbs4pxMyc5pk/nCkQ==",
|
||||
"dev": true
|
||||
},
|
||||
"nanoid": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-1.0.2.tgz",
|
||||
"integrity": "sha512-sCTwJt690lduNHyqknXJp8pRwzm80neOLGaiTHU2KUJZFVSErl778NNCIivEQCX5gNT0xR1Jy3HEMe/TABT6lw=="
|
||||
},
|
||||
"nanomatch": {
|
||||
"version": "1.2.9",
|
||||
"resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.9.tgz",
|
||||
|
@ -13912,11 +13932,6 @@
|
|||
"integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=",
|
||||
"dev": true
|
||||
},
|
||||
"shortid": {
|
||||
"version": "2.2.8",
|
||||
"resolved": "https://registry.npmjs.org/shortid/-/shortid-2.2.8.tgz",
|
||||
"integrity": "sha1-AzsRfWoul1gE9vCWnb59PQs1UTE="
|
||||
},
|
||||
"signal-exit": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
|
||||
|
@ -14605,6 +14620,22 @@
|
|||
"integrity": "sha512-j5EMxnryTvKxwH2Cq+Pb43tsf6sdEgw6Pdwxk83mPaq0ToeFJt6WE4J3s5BqY7vmjlLgkgXvhtXUxo80FyBhCA==",
|
||||
"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": {
|
||||
"version": "2.3.8",
|
||||
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
|
||||
|
|
|
@ -33,10 +33,11 @@
|
|||
"markdown": "^0.5.0",
|
||||
"mkdirp": "^0.5.1",
|
||||
"moment": "^2.22.1",
|
||||
"mz": "^2.7.0",
|
||||
"nanoid": "^1.0.2",
|
||||
"nedb": "^1.8.0",
|
||||
"npm": "^5.8.0",
|
||||
"resolve-path": "^1.4.0",
|
||||
"shortid": "^2.2.8",
|
||||
"tus-node-server": "^0.2.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -93,6 +93,16 @@ body
|
|||
font-size: 12pt;
|
||||
}
|
||||
|
||||
a, a:hover
|
||||
{
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a
|
||||
{
|
||||
color: #228dd4;
|
||||
}
|
||||
|
||||
/*
|
||||
pure-g uses hardcoded sans-serif to get accurate alignment, this class
|
||||
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'),
|
||||
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/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') },
|
||||
{ path: '', component: () => import('./route/admin/Login.vue') }
|
||||
{ path: '', name: 'adminRoot', component: () => import('./route/admin/Login.vue') }
|
||||
]
|
||||
},
|
||||
{ path: '*', redirect: '/' }
|
||||
|
@ -112,14 +112,21 @@ const router = new VueRouter({
|
|||
|
||||
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)
|
||||
{
|
||||
router.push('/admin');
|
||||
router.push({ name: 'adminRoot' });
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (isAdminRoot && shared.adminToken)
|
||||
{
|
||||
router.push({ name: 'adminDefault' });
|
||||
return;
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
|
|
|
@ -31,6 +31,7 @@ export default {
|
|||
logout: 'Logout',
|
||||
cancel: 'Cancel',
|
||||
save: 'Save',
|
||||
delete: 'Delete',
|
||||
|
||||
diskspace: '%{available} disk space available of %{total} total',
|
||||
|
||||
|
@ -63,6 +64,7 @@ export default {
|
|||
|
||||
detail: {
|
||||
code: 'Code',
|
||||
codeHint: 'Direct link to the upload page for this code:',
|
||||
owner: 'Owner',
|
||||
created: 'Date created',
|
||||
description: 'Description',
|
||||
|
|
|
@ -31,6 +31,7 @@ export default {
|
|||
logout: 'Uitloggen',
|
||||
cancel: 'Annuleren',
|
||||
save: 'Opslaan',
|
||||
delete: 'Verwijderen',
|
||||
|
||||
diskspace: '%{available} schijfruimte beschikbaar van %{total} totaal',
|
||||
|
||||
|
@ -63,6 +64,7 @@ export default {
|
|||
|
||||
detail: {
|
||||
code: 'Code',
|
||||
codeHint: 'Directe link naar de upload pagina voor deze code:',
|
||||
owner: 'Eigenaar',
|
||||
created: 'Datum aangemaakt',
|
||||
description: 'Omschrijving',
|
||||
|
|
|
@ -7,6 +7,10 @@
|
|||
<label for="codeId">{{ $t('admin.codes.detail.code') }}</label>
|
||||
<input id="codeId" type="text" readonly :value="code.id" class="pure-input-2-3">
|
||||
</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">
|
||||
<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">
|
||||
</div>
|
||||
<div class="pure-form-description">
|
||||
{{ $t('admin.codes.detail.descriptionHint' )}}
|
||||
{{ $t('admin.codes.detail.descriptionHint') }}
|
||||
</div>
|
||||
|
||||
<!--
|
||||
|
@ -38,7 +42,7 @@
|
|||
<textarea id="message" type="text" v-model="code.message" class="pure-input-2-3" rows="10"></textarea>
|
||||
</div>
|
||||
<div class="pure-form-description">
|
||||
{{ $t('admin.codes.detail.messageHint' )}}
|
||||
{{ $t('admin.codes.detail.messageHint') }}
|
||||
</div>
|
||||
|
||||
<div class="pure-controls">
|
||||
|
@ -66,33 +70,62 @@ export default {
|
|||
|
||||
props: ['codeParam'],
|
||||
|
||||
created()
|
||||
{
|
||||
var self = this;
|
||||
|
||||
if (self.codeParam)
|
||||
watch: {
|
||||
'$route' (to, from)
|
||||
{
|
||||
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
|
||||
};
|
||||
var self = this;
|
||||
self.checkCode();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
created()
|
||||
{
|
||||
var self = this;
|
||||
self.checkCode();
|
||||
},
|
||||
|
||||
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()
|
||||
{
|
||||
var self = this;
|
||||
|
@ -111,7 +144,10 @@ export default {
|
|||
})
|
||||
.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) })
|
||||
.then(() =>
|
||||
|
@ -121,7 +157,4 @@ export default {
|
|||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
</style>
|
||||
</script>
|
|
@ -55,12 +55,4 @@ export default {
|
|||
.catch((error) => { shared.$emit('apiError', error, this.$router) });
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.description
|
||||
{
|
||||
font-size: 75%;
|
||||
color: #808080;
|
||||
}
|
||||
</style>
|
||||
</script>
|
|
@ -187,4 +187,9 @@ $list-padding: .2rem;
|
|||
}
|
||||
}
|
||||
|
||||
.description
|
||||
{
|
||||
font-size: 75%;
|
||||
color: #808080;
|
||||
}
|
||||
</style>
|
|
@ -49,7 +49,7 @@ export default {
|
|||
.then((response) =>
|
||||
{
|
||||
shared.adminToken = response.data;
|
||||
self.$router.push({ path: '/admin/uploads' });
|
||||
self.$router.push({ name: 'adminDefault' });
|
||||
})
|
||||
.catch((error) =>
|
||||
{
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<menu-link route="/admin/users" :title="$t('admin.menu.users')" v-if="hasAuth('manageUsers')"></menu-link>
|
||||
|
||||
<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>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
|
@ -1,21 +1,23 @@
|
|||
<template>
|
||||
<div id="uploads">
|
||||
<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 class="properties">
|
||||
<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.username }}</span></div>
|
||||
<div class="pure-u-1-3 right"><span class="text">{{ upload.created | formatDateTime }}</span></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">
|
||||
<a :href="getDownloadUrl(file)">
|
||||
<img :src="getFileIconUrl(file.name)" class="icon">
|
||||
|
@ -45,7 +47,8 @@ export default {
|
|||
data()
|
||||
{
|
||||
return {
|
||||
uploads: null
|
||||
uploads: null,
|
||||
confirmDelete: null
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -93,6 +96,39 @@ export default {
|
|||
getDownloadUrl(file)
|
||||
{
|
||||
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;
|
||||
padding: .2rem;
|
||||
}
|
||||
|
||||
|
||||
.codedescription
|
||||
{
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
||||
.right
|
||||
{
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
|
||||
.confirm
|
||||
{
|
||||
color: red;
|
||||
}
|
||||
</style>
|
Loading…
Reference in New Issue