mirror of
https://github.com/wekan/wekan.git
synced 2026-01-27 19:56:09 +01:00
Multi-File Storage.
Thanks to mfilser ! Related https://github.com/wekan/wekan/pull/4484 Merge branch 'master' into upgrade-meteor
This commit is contained in:
commit
68e8155805
29 changed files with 921 additions and 276 deletions
|
|
@ -49,17 +49,7 @@ template(name="attachmentsGalery")
|
|||
if currentUser.isBoardMember
|
||||
unless currentUser.isCommentOnly
|
||||
unless currentUser.isWorker
|
||||
if isImage
|
||||
a(class="{{#if $eq ../coverId _id}}js-remove-cover{{else}}js-add-cover{{/if}}")
|
||||
i.fa.fa-thumb-tack
|
||||
if($eq ../coverId _id)
|
||||
| {{_ 'remove-cover'}}
|
||||
else
|
||||
| {{_ 'add-cover'}}
|
||||
if currentUser.isBoardAdmin
|
||||
a.js-confirm-delete
|
||||
i.fa.fa-close
|
||||
| {{_ 'delete'}}
|
||||
a.fa.fa-navicon.attachment-details-menu.js-open-attachment-menu(title="{{_ 'attachmentActionsPopup-title'}}")
|
||||
|
||||
if currentUser.isBoardMember
|
||||
unless currentUser.isCommentOnly
|
||||
|
|
@ -67,3 +57,31 @@ template(name="attachmentsGalery")
|
|||
//li.attachment-item.add-attachment
|
||||
a.js-add-attachment(title="{{_ 'add-attachment' }}")
|
||||
i.fa.fa-plus
|
||||
|
||||
template(name="attachmentActionsPopup")
|
||||
ul.pop-over-list
|
||||
li
|
||||
if isImage
|
||||
a(class="{{#if isCover}}js-remove-cover{{else}}js-add-cover{{/if}}")
|
||||
i.fa.fa-thumb-tack
|
||||
if isCover
|
||||
| {{_ 'remove-cover'}}
|
||||
else
|
||||
| {{_ 'add-cover'}}
|
||||
if currentUser.isBoardAdmin
|
||||
a.js-confirm-delete
|
||||
i.fa.fa-close
|
||||
| {{_ 'delete'}}
|
||||
p.attachment-storage
|
||||
| {{versions.original.storage}}
|
||||
|
||||
if $neq versions.original.storage "fs"
|
||||
a.js-move-storage-fs
|
||||
i.fa.fa-arrow-right
|
||||
| {{_ 'attachment-move-storage-fs'}}
|
||||
|
||||
if $neq versions.original.storage "gridfs"
|
||||
if versions.original.storage
|
||||
a.js-move-storage-gridfs
|
||||
i.fa.fa-arrow-right
|
||||
| {{_ 'attachment-move-storage-gridfs'}}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,11 @@
|
|||
Template.attachmentsGalery.events({
|
||||
'click .js-add-attachment': Popup.open('cardAttachments'),
|
||||
'click .js-confirm-delete': Popup.afterConfirm(
|
||||
'attachmentDelete',
|
||||
function() {
|
||||
Attachments.remove(this._id);
|
||||
Popup.back();
|
||||
},
|
||||
),
|
||||
// If we let this event bubble, FlowRouter will handle it and empty the page
|
||||
// content, see #101.
|
||||
'click .js-download'(event) {
|
||||
event.stopPropagation();
|
||||
},
|
||||
'click .js-add-cover'() {
|
||||
Cards.findOne(this.meta.cardId).setCover(this._id);
|
||||
},
|
||||
'click .js-remove-cover'() {
|
||||
Cards.findOne(this.meta.cardId).unsetCover();
|
||||
},
|
||||
'click .js-open-attachment-menu': Popup.open('attachmentActions'),
|
||||
});
|
||||
|
||||
Template.attachmentsGalery.helpers({
|
||||
|
|
@ -33,12 +21,16 @@ Template.cardAttachmentsPopup.events({
|
|||
'change .js-attach-file'(event) {
|
||||
const card = this;
|
||||
if (event.currentTarget.files && event.currentTarget.files[0]) {
|
||||
const fileId = Random.id();
|
||||
const config = {
|
||||
file: event.currentTarget.files[0],
|
||||
fileId: fileId,
|
||||
meta: Utils.getCommonAttachmentMetaFrom(card),
|
||||
chunkSize: 'dynamic',
|
||||
};
|
||||
config.meta.fileId = fileId;
|
||||
const uploader = Attachments.insert(
|
||||
{
|
||||
file: event.currentTarget.files[0],
|
||||
meta: Utils.getCommonAttachmentMetaFrom(card),
|
||||
chunkSize: 'dynamic',
|
||||
},
|
||||
config,
|
||||
false,
|
||||
);
|
||||
uploader.on('uploaded', (error, fileRef) => {
|
||||
|
|
@ -104,13 +96,17 @@ Template.previewClipboardImagePopup.events({
|
|||
if (pastedResults && pastedResults.file) {
|
||||
const file = pastedResults.file;
|
||||
window.oPasted = pastedResults;
|
||||
const fileId = Random.id();
|
||||
const config = {
|
||||
file,
|
||||
fileId: fileId,
|
||||
meta: Utils.getCommonAttachmentMetaFrom(card),
|
||||
fileName: file.name || file.type.replace('image/', 'clipboard.'),
|
||||
chunkSize: 'dynamic',
|
||||
};
|
||||
config.meta.fileId = fileId;
|
||||
const uploader = Attachments.insert(
|
||||
{
|
||||
file,
|
||||
meta: Utils.getCommonAttachmentMetaFrom(card),
|
||||
fileName: file.name || file.type.replace('image/', 'clipboard.'),
|
||||
chunkSize: 'dynamic',
|
||||
},
|
||||
config,
|
||||
false,
|
||||
);
|
||||
uploader.on('uploaded', (error, fileRef) => {
|
||||
|
|
@ -129,3 +125,36 @@ Template.previewClipboardImagePopup.events({
|
|||
}
|
||||
},
|
||||
});
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
isCover() {
|
||||
const ret = Cards.findOne(this.data().meta.cardId).coverId == this.data()._id;
|
||||
return ret;
|
||||
},
|
||||
events() {
|
||||
return [
|
||||
{
|
||||
'click .js-confirm-delete': Popup.afterConfirm('attachmentDelete', function() {
|
||||
Attachments.remove(this._id);
|
||||
Popup.back(2);
|
||||
}),
|
||||
'click .js-add-cover'() {
|
||||
Cards.findOne(this.data().meta.cardId).setCover(this.data()._id);
|
||||
Popup.back();
|
||||
},
|
||||
'click .js-remove-cover'() {
|
||||
Cards.findOne(this.data().meta.cardId).unsetCover();
|
||||
Popup.back();
|
||||
},
|
||||
'click .js-move-storage-fs'() {
|
||||
Meteor.call('moveAttachmentToStorage', this.data()._id, "fs");
|
||||
Popup.back();
|
||||
},
|
||||
'click .js-move-storage-gridfs'() {
|
||||
Meteor.call('moveAttachmentToStorage', this.data()._id, "gridfs");
|
||||
Popup.back();
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
||||
}).register('attachmentActionsPopup');
|
||||
|
|
|
|||
|
|
@ -46,6 +46,9 @@
|
|||
.attachment-details-actions a
|
||||
display: block
|
||||
|
||||
&.attachment-details-menu
|
||||
padding-top: 10px
|
||||
|
||||
.attachment-image-preview
|
||||
max-width: 100px
|
||||
display: block
|
||||
|
|
|
|||
|
|
@ -11,11 +11,6 @@ template(name="adminReports")
|
|||
i.fa.fa-chain-broken
|
||||
| {{_ 'broken-cards'}}
|
||||
|
||||
li
|
||||
a.js-report-files(data-id="report-orphaned-files")
|
||||
i.fa.fa-paperclip
|
||||
| {{_ 'orphanedFilesReportTitle'}}
|
||||
|
||||
li
|
||||
a.js-report-files(data-id="report-files")
|
||||
i.fa.fa-paperclip
|
||||
|
|
@ -43,8 +38,6 @@ template(name="adminReports")
|
|||
+brokenCardsReport
|
||||
else if showFilesReport.get
|
||||
+filesReport
|
||||
else if showOrphanedFilesReport.get
|
||||
+orphanedFilesReport
|
||||
else if showRulesReport.get
|
||||
+rulesReport
|
||||
else if showBoardsReport.get
|
||||
|
|
@ -64,7 +57,7 @@ template(name="brokenCardsReport")
|
|||
template(name="rulesReport")
|
||||
h1 {{_ 'rulesReportTitle'}}
|
||||
if resultsCount
|
||||
table.table
|
||||
table
|
||||
tr
|
||||
th Rule Title
|
||||
th Board Title
|
||||
|
|
@ -83,44 +76,23 @@ template(name="rulesReport")
|
|||
template(name="filesReport")
|
||||
h1 {{_ 'filesReportTitle'}}
|
||||
if resultsCount
|
||||
table.table
|
||||
table
|
||||
tr
|
||||
th Filename
|
||||
th.right Size (kB)
|
||||
th MIME Type
|
||||
th.center Usage
|
||||
th MD5 Sum
|
||||
th ID
|
||||
th Attachment ID
|
||||
th Board ID
|
||||
th Card ID
|
||||
|
||||
each att in results
|
||||
tr
|
||||
td {{ att.filename }}
|
||||
td.right {{fileSize att.length }}
|
||||
td {{ att.contentType }}
|
||||
td.center {{usageCount att._id.toHexString }}
|
||||
td {{ att.md5 }}
|
||||
td {{ att._id.toHexString }}
|
||||
else
|
||||
div {{_ 'no-results' }}
|
||||
|
||||
template(name="orphanedFilesReport")
|
||||
h1 {{_ 'orphanedFilesReportTitle'}}
|
||||
if resultsCount
|
||||
table.table
|
||||
tr
|
||||
th Filename
|
||||
th.right Size (kB)
|
||||
th MIME Type
|
||||
th MD5 Sum
|
||||
th ID
|
||||
|
||||
each att in results
|
||||
tr
|
||||
td {{ att.filename }}
|
||||
td.right {{fileSize att.length }}
|
||||
td {{ att.contentType }}
|
||||
td {{ att.md5 }}
|
||||
td {{ att._id.toHexString }}
|
||||
td {{ att.name }}
|
||||
td.right {{fileSize att.size }}
|
||||
td {{ att.type }}
|
||||
td {{ att._id }}
|
||||
td {{ att.meta.boardId }}
|
||||
td {{ att.meta.cardId }}
|
||||
else
|
||||
div {{_ 'no-results' }}
|
||||
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ BlazeComponent.extendComponent({
|
|||
{
|
||||
'click a.js-report-broken': this.switchMenu,
|
||||
'click a.js-report-files': this.switchMenu,
|
||||
'click a.js-report-orphaned-files': this.switchMenu,
|
||||
'click a.js-report-rules': this.switchMenu,
|
||||
'click a.js-report-cards': this.switchMenu,
|
||||
'click a.js-report-boards': this.switchMenu,
|
||||
|
|
@ -66,11 +65,6 @@ BlazeComponent.extendComponent({
|
|||
this.subscription = Meteor.subscribe('attachmentsList', () => {
|
||||
this.loading.set(false);
|
||||
});
|
||||
} else if ('report-orphaned-files' === targetID) {
|
||||
this.showOrphanedFilesReport.set(true);
|
||||
this.subscription = Meteor.subscribe('orphanedAttachments', () => {
|
||||
this.loading.set(false);
|
||||
});
|
||||
} else if ('report-rules' === targetID) {
|
||||
this.subscription = Meteor.subscribe('rulesReport', () => {
|
||||
this.showRulesReport.set(true);
|
||||
|
|
@ -104,8 +98,6 @@ class AdminReport extends BlazeComponent {
|
|||
|
||||
results() {
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('attachments:', AttachmentStorage.find());
|
||||
// console.log('attachments.count:', AttachmentStorage.find().count());
|
||||
return this.collection.find();
|
||||
}
|
||||
|
||||
|
|
@ -125,10 +117,6 @@ class AdminReport extends BlazeComponent {
|
|||
return Math.round(size / 1024);
|
||||
}
|
||||
|
||||
usageCount(key) {
|
||||
return Attachments.find({ 'copies.attachments.key': key }).count();
|
||||
}
|
||||
|
||||
abbreviate(text) {
|
||||
if (text.length > 30) {
|
||||
return `${text.substr(0, 29)}...`;
|
||||
|
|
@ -138,13 +126,9 @@ class AdminReport extends BlazeComponent {
|
|||
}
|
||||
|
||||
(class extends AdminReport {
|
||||
collection = AttachmentStorage;
|
||||
collection = Attachments;
|
||||
}.register('filesReport'));
|
||||
|
||||
(class extends AdminReport {
|
||||
collection = AttachmentStorage;
|
||||
}.register('orphanedFilesReport'));
|
||||
|
||||
(class extends AdminReport {
|
||||
collection = Rules;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
.admin-reports-content
|
||||
height: auto !important
|
||||
|
||||
84
client/components/settings/attachments.jade
Normal file
84
client/components/settings/attachments.jade
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
template(name="attachments")
|
||||
.setting-content.attachments-content
|
||||
unless currentUser.isAdmin
|
||||
| {{_ 'error-notAuthorized'}}
|
||||
else
|
||||
.content-body
|
||||
.side-menu
|
||||
ul
|
||||
li
|
||||
a.js-move-attachments(data-id="move-attachments")
|
||||
i.fa.fa-arrow-right
|
||||
| {{_ 'attachment-move'}}
|
||||
|
||||
.main-body
|
||||
if loading.get
|
||||
+spinner
|
||||
else if showMoveAttachments.get
|
||||
+moveAttachments
|
||||
|
||||
template(name="moveAttachments")
|
||||
.move-attachment-buttons
|
||||
.js-move-attachment
|
||||
button.js-move-all-attachments-to-fs {{_ 'move-all-attachments-to-fs'}}
|
||||
.js-move-attachment
|
||||
button.js-move-all-attachments-to-gridfs {{_ 'move-all-attachments-to-gridfs'}}
|
||||
|
||||
each board in getBoardsWithAttachments
|
||||
+moveBoardAttachments board
|
||||
|
||||
template(name="moveBoardAttachments")
|
||||
hr
|
||||
.board-description
|
||||
table
|
||||
tr
|
||||
th {{_ 'board'}} ID
|
||||
th {{_ 'board-title'}}
|
||||
tr
|
||||
td {{ _id }}
|
||||
td {{ title }}
|
||||
|
||||
.move-attachment-buttons
|
||||
.js-move-attachment
|
||||
button.js-move-all-attachments-of-board-to-fs {{_ 'move-all-attachments-of-board-to-fs'}}
|
||||
.js-move-attachment
|
||||
button.js-move-all-attachments-of-board-to-gridfs {{_ 'move-all-attachments-of-board-to-gridfs'}}
|
||||
|
||||
.board-attachments
|
||||
table
|
||||
tr
|
||||
th {{_ 'card'}}-Id
|
||||
th {{_ 'attachment'}}-Id
|
||||
th {{_ 'name'}}
|
||||
th {{_ 'path'}}
|
||||
th {{_ 'version-name'}}
|
||||
th {{_ 'size'}} (B)
|
||||
th GridFsFileId
|
||||
th {{_ 'storage'}}
|
||||
th {{_ 'action'}}
|
||||
|
||||
each attachment in attachments
|
||||
+moveAttachment attachment
|
||||
|
||||
template(name="moveAttachment")
|
||||
each version in flatVersion
|
||||
tr
|
||||
td {{ meta.cardId }}
|
||||
td {{ _id }}
|
||||
td {{ name }}
|
||||
td {{ version.path }}
|
||||
td {{ version.versionName }}
|
||||
td {{ version.size }}
|
||||
td {{ version.meta.gridFsFileId }}
|
||||
td {{ version.storageName }}
|
||||
td
|
||||
if $neq version.storageName "fs"
|
||||
button.js-move-storage-fs
|
||||
i.fa.fa-arrow-right
|
||||
| {{_ 'attachment-move-storage-fs'}}
|
||||
|
||||
if $neq version.storageName "gridfs"
|
||||
if version.storageName
|
||||
button.js-move-storage-gridfs
|
||||
i.fa.fa-arrow-right
|
||||
| {{_ 'attachment-move-storage-gridfs'}}
|
||||
123
client/components/settings/attachments.js
Normal file
123
client/components/settings/attachments.js
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
import Attachments, { fileStoreStrategyFactory } from '/models/attachments';
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
subscription: null,
|
||||
showMoveAttachments: new ReactiveVar(false),
|
||||
sessionId: null,
|
||||
|
||||
onCreated() {
|
||||
this.error = new ReactiveVar('');
|
||||
this.loading = new ReactiveVar(false);
|
||||
},
|
||||
|
||||
events() {
|
||||
return [
|
||||
{
|
||||
'click a.js-move-attachments': this.switchMenu,
|
||||
},
|
||||
];
|
||||
},
|
||||
|
||||
switchMenu(event) {
|
||||
const target = $(event.target);
|
||||
if (!target.hasClass('active')) {
|
||||
this.loading.set(true);
|
||||
this.showMoveAttachments.set(false);
|
||||
if (this.subscription) {
|
||||
this.subscription.stop();
|
||||
}
|
||||
|
||||
$('.side-menu li.active').removeClass('active');
|
||||
target.parent().addClass('active');
|
||||
const targetID = target.data('id');
|
||||
|
||||
if ('move-attachments' === targetID) {
|
||||
this.showMoveAttachments.set(true);
|
||||
this.subscription = Meteor.subscribe('attachmentsList', () => {
|
||||
this.loading.set(false);
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
}).register('attachments');
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
getBoardsWithAttachments() {
|
||||
this.attachments = Attachments.find().get();
|
||||
this.attachmentsByBoardId = _.chain(this.attachments)
|
||||
.groupBy(fileObj => fileObj.meta.boardId)
|
||||
.value();
|
||||
|
||||
const ret = Object.keys(this.attachmentsByBoardId)
|
||||
.map(boardId => {
|
||||
const boardAttachments = this.attachmentsByBoardId[boardId];
|
||||
|
||||
_.each(boardAttachments, _attachment => {
|
||||
_attachment.flatVersion = Object.keys(_attachment.versions)
|
||||
.map(_versionName => {
|
||||
const _version = Object.assign(_attachment.versions[_versionName], {"versionName": _versionName});
|
||||
_version.storageName = fileStoreStrategyFactory.getFileStrategy(_attachment, _versionName).getStorageName();
|
||||
return _version;
|
||||
});
|
||||
});
|
||||
const board = Boards.findOne(boardId);
|
||||
board.attachments = boardAttachments;
|
||||
return board;
|
||||
})
|
||||
return ret;
|
||||
},
|
||||
getBoardData(boardid) {
|
||||
const ret = Boards.findOne(boardId);
|
||||
return ret;
|
||||
},
|
||||
events() {
|
||||
return [
|
||||
{
|
||||
'click button.js-move-all-attachments-to-fs'(event) {
|
||||
this.attachments.forEach(_attachment => {
|
||||
Meteor.call('moveAttachmentToStorage', _attachment._id, "fs");
|
||||
});
|
||||
},
|
||||
'click button.js-move-all-attachments-to-gridfs'(event) {
|
||||
this.attachments.forEach(_attachment => {
|
||||
Meteor.call('moveAttachmentToStorage', _attachment._id, "gridfs");
|
||||
});
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
||||
}).register('moveAttachments');
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
events() {
|
||||
return [
|
||||
{
|
||||
'click button.js-move-all-attachments-of-board-to-fs'(event) {
|
||||
this.data().attachments.forEach(_attachment => {
|
||||
Meteor.call('moveAttachmentToStorage', _attachment._id, "fs");
|
||||
});
|
||||
},
|
||||
'click button.js-move-all-attachments-of-board-to-gridfs'(event) {
|
||||
this.data().attachments.forEach(_attachment => {
|
||||
Meteor.call('moveAttachmentToStorage', _attachment._id, "gridfs");
|
||||
});
|
||||
},
|
||||
}
|
||||
]
|
||||
},
|
||||
}).register('moveBoardAttachments');
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
events() {
|
||||
return [
|
||||
{
|
||||
'click button.js-move-storage-fs'(event) {
|
||||
Meteor.call('moveAttachmentToStorage', this.data()._id, "fs");
|
||||
},
|
||||
'click button.js-move-storage-gridfs'(event) {
|
||||
Meteor.call('moveAttachmentToStorage', this.data()._id, "gridfs");
|
||||
},
|
||||
}
|
||||
]
|
||||
},
|
||||
}).register('moveAttachment');
|
||||
8
client/components/settings/attachments.styl
Normal file
8
client/components/settings/attachments.styl
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
.move-attachment-buttons
|
||||
display: flex
|
||||
gap: 10px
|
||||
|
||||
.attachments-content
|
||||
hr
|
||||
height: 0px
|
||||
border: 1px solid black
|
||||
|
|
@ -2,8 +2,6 @@
|
|||
overflow: scroll;
|
||||
|
||||
table
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
color: #000;
|
||||
|
||||
td, th
|
||||
|
|
@ -22,14 +20,13 @@ table
|
|||
.ext-box-left
|
||||
display: flex;
|
||||
width: 100%
|
||||
gap: 10px
|
||||
|
||||
span
|
||||
vertical-align: center;
|
||||
line-height: 34px;
|
||||
margin-right: 10px;
|
||||
|
||||
input, button
|
||||
margin: 0 10px 0 0;
|
||||
padding: 0;
|
||||
|
||||
button
|
||||
|
|
|
|||
|
|
@ -7,11 +7,9 @@
|
|||
display: flex
|
||||
|
||||
.setting-content
|
||||
padding 30px
|
||||
color: #727479
|
||||
background: #dedede
|
||||
width 100%
|
||||
height calc(100% - 80px)
|
||||
position: absolute;
|
||||
|
||||
.content-title
|
||||
|
|
@ -21,6 +19,7 @@
|
|||
display flex
|
||||
padding-top 15px
|
||||
height 100%
|
||||
gap: 10px;
|
||||
|
||||
.side-menu
|
||||
background-color: #f7f7f7;
|
||||
|
|
@ -54,7 +53,6 @@
|
|||
margin-right: 20px
|
||||
|
||||
.main-body
|
||||
padding: 0.1em 1em
|
||||
-webkit-user-select: text // Safari 3.1+
|
||||
-moz-user-select: text // Firefox 2+
|
||||
-ms-user-select: text // IE 10+
|
||||
|
|
|
|||
|
|
@ -16,6 +16,10 @@ template(name="settingHeaderBar")
|
|||
i.fa(class="fa-list")
|
||||
span {{_ 'reports'}}
|
||||
|
||||
a.setting-header-btn.informations(href="{{pathFor 'attachments'}}")
|
||||
i.fa(class="fa-paperclip")
|
||||
span {{_ 'attachments'}}
|
||||
|
||||
a.setting-header-btn.informations(href="{{pathFor 'information'}}")
|
||||
i.fa(class="fa-info-circle")
|
||||
span {{_ 'info'}}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue