mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-11-06 14:05:46 +01:00
closes #6247
This commit is contained in:
7
public/language/en-GB/admin/manage/uploads.json
Normal file
7
public/language/en-GB/admin/manage/uploads.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"upload-file": "Upload File",
|
||||||
|
"filename": "Filename",
|
||||||
|
"size/filecount": "Size / Filecount",
|
||||||
|
"confirm-delete": "Do you really want to delete this file?",
|
||||||
|
"filecount": "%1 files"
|
||||||
|
}
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
"manage/post-queue": "Post Queue",
|
"manage/post-queue": "Post Queue",
|
||||||
"manage/groups": "Groups",
|
"manage/groups": "Groups",
|
||||||
"manage/ip-blacklist": "IP Blacklist",
|
"manage/ip-blacklist": "IP Blacklist",
|
||||||
|
"manage/uploads": "Uploads",
|
||||||
|
|
||||||
"section-settings": "Settings",
|
"section-settings": "Settings",
|
||||||
"settings/general": "General",
|
"settings/general": "General",
|
||||||
|
|||||||
35
public/src/admin/manage/uploads.js
Normal file
35
public/src/admin/manage/uploads.js
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
|
||||||
|
define('admin/manage/uploads', ['uploader'], function (uploader) {
|
||||||
|
var Uploads = {};
|
||||||
|
|
||||||
|
Uploads.init = function () {
|
||||||
|
$('#upload').on('click', function () {
|
||||||
|
uploader.show({
|
||||||
|
title: '[[admin/manage/uploads:upload-file]]',
|
||||||
|
route: config.relative_path + '/api/admin/upload/file',
|
||||||
|
params: { folder: ajaxify.data.currentFolder },
|
||||||
|
}, function () {
|
||||||
|
ajaxify.refresh();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.delete').on('click', function () {
|
||||||
|
var file = $(this).parents('[data-path]');
|
||||||
|
bootbox.confirm('[[admin/manage/uploads:confirm-delete]]', function (ok) {
|
||||||
|
if (!ok) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
socket.emit('admin.uploads.delete', file.attr('data-path'), function (err) {
|
||||||
|
if (err) {
|
||||||
|
return app.alertError(err.message);
|
||||||
|
}
|
||||||
|
file.remove();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return Uploads;
|
||||||
|
});
|
||||||
@@ -4,16 +4,109 @@ var path = require('path');
|
|||||||
var async = require('async');
|
var async = require('async');
|
||||||
var nconf = require('nconf');
|
var nconf = require('nconf');
|
||||||
var mime = require('mime');
|
var mime = require('mime');
|
||||||
|
var fs = require('fs');
|
||||||
|
|
||||||
var meta = require('../../meta');
|
var meta = require('../../meta');
|
||||||
var file = require('../../file');
|
var file = require('../../file');
|
||||||
var image = require('../../image');
|
var image = require('../../image');
|
||||||
var plugins = require('../../plugins');
|
var plugins = require('../../plugins');
|
||||||
|
var pagination = require('../../pagination');
|
||||||
|
|
||||||
var allowedImageTypes = ['image/png', 'image/jpeg', 'image/pjpeg', 'image/jpg', 'image/gif', 'image/svg+xml'];
|
var allowedImageTypes = ['image/png', 'image/jpeg', 'image/pjpeg', 'image/jpg', 'image/gif', 'image/svg+xml'];
|
||||||
|
|
||||||
var uploadsController = module.exports;
|
var uploadsController = module.exports;
|
||||||
|
|
||||||
|
uploadsController.get = function (req, res, next) {
|
||||||
|
var currentFolder = path.join(nconf.get('upload_path'), req.query.dir || '');
|
||||||
|
if (!currentFolder.startsWith(nconf.get('upload_path'))) {
|
||||||
|
return next(new Error('[[error:invalid-path]]'));
|
||||||
|
}
|
||||||
|
var itemsPerPage = 20;
|
||||||
|
var itemCount = 0;
|
||||||
|
var page = parseInt(req.query.page, 10) || 1;
|
||||||
|
async.waterfall([
|
||||||
|
function (next) {
|
||||||
|
fs.readdir(currentFolder, next);
|
||||||
|
},
|
||||||
|
function (files, next) {
|
||||||
|
files = files.filter(function (filename) {
|
||||||
|
return filename !== '.gitignore';
|
||||||
|
});
|
||||||
|
|
||||||
|
itemCount = files.length;
|
||||||
|
var start = Math.max(0, (page - 1) * itemsPerPage);
|
||||||
|
var stop = start + itemsPerPage;
|
||||||
|
files = files.slice(start, stop);
|
||||||
|
|
||||||
|
filesToData(currentFolder, files, next);
|
||||||
|
},
|
||||||
|
function (files) {
|
||||||
|
files.sort(function (a, b) {
|
||||||
|
if (a.isDirectory && !b.isDirectory) {
|
||||||
|
return -1;
|
||||||
|
} else if (!a.isDirectory && b.isDirectory) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
res.render('admin/manage/uploads', {
|
||||||
|
currentFolder: currentFolder.replace(nconf.get('upload_path'), ''),
|
||||||
|
files: files,
|
||||||
|
breadcrumbs: buildBreadcrumbs(currentFolder),
|
||||||
|
pagination: pagination.create(page, Math.ceil(itemCount / itemsPerPage), req.query),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
], next);
|
||||||
|
};
|
||||||
|
|
||||||
|
function buildBreadcrumbs(currentFolder) {
|
||||||
|
var crumbs = [];
|
||||||
|
var parts = currentFolder.replace(nconf.get('upload_path'), '').split(path.sep);
|
||||||
|
var currentPath = '';
|
||||||
|
parts.forEach(function (part) {
|
||||||
|
var dir = path.join(currentPath, part);
|
||||||
|
crumbs.push({
|
||||||
|
text: part || 'Uploads',
|
||||||
|
url: part ? '/admin/manage/uploads?dir=' + dir : '/admin/manage/uploads',
|
||||||
|
});
|
||||||
|
currentPath = dir;
|
||||||
|
});
|
||||||
|
|
||||||
|
return crumbs;
|
||||||
|
}
|
||||||
|
|
||||||
|
function filesToData(currentDir, files, callback) {
|
||||||
|
async.map(files, function (file, next) {
|
||||||
|
var stat;
|
||||||
|
async.waterfall([
|
||||||
|
function (next) {
|
||||||
|
fs.stat(path.join(currentDir, file), next);
|
||||||
|
},
|
||||||
|
function (_stat, next) {
|
||||||
|
stat = _stat;
|
||||||
|
if (stat.isDirectory()) {
|
||||||
|
fs.readdir(path.join(currentDir, file), next);
|
||||||
|
} else {
|
||||||
|
next(null, []);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
function (filesInDir, next) {
|
||||||
|
var url = nconf.get('upload_url') + currentDir.replace(nconf.get('upload_path'), '') + '/' + file;
|
||||||
|
next(null, {
|
||||||
|
name: file,
|
||||||
|
path: path.join(currentDir, file).replace(nconf.get('upload_path'), ''),
|
||||||
|
url: url,
|
||||||
|
fileCount: filesInDir.length - 1, // ignore .gitignore
|
||||||
|
size: stat.size,
|
||||||
|
sizeHumanReadable: (stat.size / 1024).toFixed(1) + 'KiB',
|
||||||
|
isDirectory: stat.isDirectory(),
|
||||||
|
isFile: stat.isFile(),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
], next);
|
||||||
|
}, callback);
|
||||||
|
}
|
||||||
|
|
||||||
uploadsController.uploadCategoryPicture = function (req, res, next) {
|
uploadsController.uploadCategoryPicture = function (req, res, next) {
|
||||||
var uploadedFile = req.files.files[0];
|
var uploadedFile = req.files.files[0];
|
||||||
var params = null;
|
var params = null;
|
||||||
@@ -110,6 +203,25 @@ uploadsController.uploadSound = function (req, res, next) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
uploadsController.uploadFile = function (req, res, next) {
|
||||||
|
var uploadedFile = req.files.files[0];
|
||||||
|
var params;
|
||||||
|
try {
|
||||||
|
params = JSON.parse(req.body.params);
|
||||||
|
} catch (e) {
|
||||||
|
file.delete(uploadedFile.path);
|
||||||
|
return next(new Error('[[error:invalid-json]]'));
|
||||||
|
}
|
||||||
|
|
||||||
|
file.saveFileToLocal(uploadedFile.name, params.folder, uploadedFile.path, function (err, data) {
|
||||||
|
file.delete(uploadedFile.path);
|
||||||
|
if (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
res.json([{ url: data.url }]);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
uploadsController.uploadDefaultAvatar = function (req, res, next) {
|
uploadsController.uploadDefaultAvatar = function (req, res, next) {
|
||||||
upload('avatar-default', req, res, next);
|
upload('avatar-default', req, res, next);
|
||||||
};
|
};
|
||||||
@@ -173,3 +285,4 @@ function uploadImage(filename, folder, uploadedFile, req, res, next) {
|
|||||||
res.json([{ name: uploadedFile.name, url: image.url.startsWith('http') ? image.url : nconf.get('relative_path') + image.url }]);
|
res.json([{ name: uploadedFile.name, url: image.url.startsWith('http') ? image.url : nconf.get('relative_path') + image.url }]);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ file.saveFileToLocal = function (filename, folder, tempPath, callback) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
callback(null, {
|
callback(null, {
|
||||||
url: '/assets/uploads/' + folder + '/' + filename,
|
url: '/assets/uploads/' + (folder ? folder + '/' : '') + filename,
|
||||||
path: uploadPath,
|
path: uploadPath,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
var qs = require('querystring');
|
var qs = require('querystring');
|
||||||
var _ = require('lodash');
|
var _ = require('lodash');
|
||||||
|
|
||||||
var pagination = {};
|
var pagination = module.exports;
|
||||||
|
|
||||||
pagination.create = function (currentPage, pageCount, queryObj) {
|
pagination.create = function (currentPage, pageCount, queryObj) {
|
||||||
if (pageCount <= 1) {
|
if (pageCount <= 1) {
|
||||||
@@ -76,6 +76,3 @@ pagination.create = function (currentPage, pageCount, queryObj) {
|
|||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
module.exports = pagination;
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ function apiRoutes(router, middleware, controllers) {
|
|||||||
router.post('/uploadlogo', middlewares, controllers.admin.uploads.uploadLogo);
|
router.post('/uploadlogo', middlewares, controllers.admin.uploads.uploadLogo);
|
||||||
router.post('/uploadOgImage', middlewares, controllers.admin.uploads.uploadOgImage);
|
router.post('/uploadOgImage', middlewares, controllers.admin.uploads.uploadOgImage);
|
||||||
router.post('/upload/sound', middlewares, controllers.admin.uploads.uploadSound);
|
router.post('/upload/sound', middlewares, controllers.admin.uploads.uploadSound);
|
||||||
|
router.post('/upload/file', middlewares, controllers.admin.uploads.uploadFile);
|
||||||
router.post('/uploadDefaultAvatar', middlewares, controllers.admin.uploads.uploadDefaultAvatar);
|
router.post('/uploadDefaultAvatar', middlewares, controllers.admin.uploads.uploadDefaultAvatar);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,6 +78,8 @@ function addRoutes(router, middleware, controllers) {
|
|||||||
router.get('/manage/groups', middlewares, controllers.admin.groups.list);
|
router.get('/manage/groups', middlewares, controllers.admin.groups.list);
|
||||||
router.get('/manage/groups/:name', middlewares, controllers.admin.groups.get);
|
router.get('/manage/groups/:name', middlewares, controllers.admin.groups.get);
|
||||||
|
|
||||||
|
router.get('/manage/uploads', middlewares, controllers.admin.uploads.get);
|
||||||
|
|
||||||
router.get('/settings/:term?', middlewares, controllers.admin.settings.get);
|
router.get('/settings/:term?', middlewares, controllers.admin.settings.get);
|
||||||
|
|
||||||
router.get('/appearance/:term?', middlewares, controllers.admin.appearance.get);
|
router.get('/appearance/:term?', middlewares, controllers.admin.appearance.get);
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
var async = require('async');
|
var async = require('async');
|
||||||
var winston = require('winston');
|
var winston = require('winston');
|
||||||
|
var fs = require('fs');
|
||||||
|
var path = require('path');
|
||||||
|
var nconf = require('nconf');
|
||||||
|
|
||||||
var meta = require('../meta');
|
var meta = require('../meta');
|
||||||
var plugins = require('../plugins');
|
var plugins = require('../plugins');
|
||||||
@@ -37,6 +40,7 @@ var SocketAdmin = {
|
|||||||
analytics: {},
|
analytics: {},
|
||||||
logs: {},
|
logs: {},
|
||||||
errors: {},
|
errors: {},
|
||||||
|
uploads: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
SocketAdmin.before = function (socket, method, data, next) {
|
SocketAdmin.before = function (socket, method, data, next) {
|
||||||
@@ -336,4 +340,13 @@ SocketAdmin.reloadAllSessions = function (socket, data, callback) {
|
|||||||
callback();
|
callback();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
SocketAdmin.uploads.delete = function (socket, pathToFile, callback) {
|
||||||
|
pathToFile = path.join(nconf.get('upload_path'), pathToFile);
|
||||||
|
if (!pathToFile.startsWith(nconf.get('upload_path'))) {
|
||||||
|
return callback(new Error('[[error:invalid-path]]'));
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.unlink(pathToFile, callback);
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = SocketAdmin;
|
module.exports = SocketAdmin;
|
||||||
|
|||||||
39
src/views/admin/manage/uploads.tpl
Normal file
39
src/views/admin/manage/uploads.tpl
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<!-- IMPORT partials/breadcrumbs.tpl -->
|
||||||
|
<div class="clearfix">
|
||||||
|
<button id="upload" class="btn-success pull-right"><i class="fa fa-upload"></i> [[global:upload]]</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-striped users-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>[[admin/manage/uploads:filename]]</th>
|
||||||
|
<th class="text-right">[[admin/manage/uploads:size/filecount]]</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<!-- BEGIN files -->
|
||||||
|
<tr data-path="{files.path}">
|
||||||
|
<!-- IF files.isDirectory -->
|
||||||
|
<td class="col-md-9" role="button">
|
||||||
|
<i class="fa fa-fw fa-folder-o"></i> <a href="{config.relative}/admin/manage/uploads?dir={files.path}">{files.name}</a>
|
||||||
|
</td>
|
||||||
|
<!-- ENDIF files.isDirectory -->
|
||||||
|
|
||||||
|
<!-- IF files.isFile -->
|
||||||
|
<td class="col-md-9">
|
||||||
|
<i class="fa fa-fw fa-file-text-o"></i> <a href="{config.relative_path}{files.url}" target="_blank">{files.name}</a>
|
||||||
|
</td>
|
||||||
|
<!-- ENDIF files.isFile -->
|
||||||
|
|
||||||
|
<td class="col-md-2 text-right"><!-- IF files.size -->{files.sizeHumanReadable}<!-- ELSE -->[[admin/manage/uploads:filecount, {files.fileCount}]]<!-- ENDIF files.size --></td>
|
||||||
|
|
||||||
|
<td role="button" class="col-md-1 text-right"><i class="delete fa fa-fw fa-trash-o <!-- IF !files.isFile --> hidden<!-- ENDIF !files.isFile -->"></i></td>
|
||||||
|
</tr>
|
||||||
|
<!-- END files -->
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- IMPORT partials/paginator.tpl -->
|
||||||
@@ -23,6 +23,7 @@
|
|||||||
<li><a href="{relative_path}/admin/manage/registration">[[admin/menu:manage/registration]]</a></li>
|
<li><a href="{relative_path}/admin/manage/registration">[[admin/menu:manage/registration]]</a></li>
|
||||||
<li><a href="{relative_path}/admin/manage/post-queue">[[admin/menu:manage/post-queue]]</a></li>
|
<li><a href="{relative_path}/admin/manage/post-queue">[[admin/menu:manage/post-queue]]</a></li>
|
||||||
<li><a href="{relative_path}/admin/manage/ip-blacklist">[[admin/menu:manage/ip-blacklist]]</a></li>
|
<li><a href="{relative_path}/admin/manage/ip-blacklist">[[admin/menu:manage/ip-blacklist]]</a></li>
|
||||||
|
<li><a href="{relative_path}/admin/manage/uploads">[[admin/menu:manage/uploads]]</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -198,6 +199,7 @@
|
|||||||
<li><a href="{relative_path}/admin/manage/registration">[[admin/menu:manage/registration]]</a></li>
|
<li><a href="{relative_path}/admin/manage/registration">[[admin/menu:manage/registration]]</a></li>
|
||||||
<li><a href="{relative_path}/admin/manage/post-queue">[[admin/menu:manage/post-queue]]</a></li>
|
<li><a href="{relative_path}/admin/manage/post-queue">[[admin/menu:manage/post-queue]]</a></li>
|
||||||
<li><a href="{relative_path}/admin/manage/ip-blacklist">[[admin/menu:manage/ip-blacklist]]</a></li>
|
<li><a href="{relative_path}/admin/manage/ip-blacklist">[[admin/menu:manage/ip-blacklist]]</a></li>
|
||||||
|
<li><a href="{relative_path}/admin/manage/uploads">[[admin/menu:manage/uploads]]</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li class="dropdown menu-item">
|
<li class="dropdown menu-item">
|
||||||
|
|||||||
Reference in New Issue
Block a user