mirror of
https://github.com/wekan/wekan.git
synced 2026-01-18 23:36:10 +01:00
Fixed attachments migrations at Admin Panel to not use too much CPU while migrating attachments.
Thanks to xet7 !
This commit is contained in:
parent
de77776cd0
commit
d59683eff1
8 changed files with 1637 additions and 0 deletions
200
client/components/settings/attachmentSettings.jade
Normal file
200
client/components/settings/attachmentSettings.jade
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
template(name="attachmentSettings")
|
||||
.setting-content.attachment-settings-content
|
||||
unless currentUser.isAdmin
|
||||
| {{_ 'error-notAuthorized'}}
|
||||
else
|
||||
.content-body
|
||||
.side-menu
|
||||
ul
|
||||
li
|
||||
a.js-attachment-storage-settings(data-id="storage-settings")
|
||||
i.fa.fa-cog
|
||||
| {{_ 'attachment-storage-settings'}}
|
||||
li
|
||||
a.js-attachment-migration(data-id="attachment-migration")
|
||||
i.fa.fa-arrow-right
|
||||
| {{_ 'attachment-migration'}}
|
||||
li
|
||||
a.js-attachment-monitoring(data-id="attachment-monitoring")
|
||||
i.fa.fa-chart-line
|
||||
| {{_ 'attachment-monitoring'}}
|
||||
|
||||
.main-body
|
||||
if loading.get
|
||||
+spinner
|
||||
else if showStorageSettings.get
|
||||
+storageSettings
|
||||
else if showMigration.get
|
||||
+attachmentMigration
|
||||
else if showMonitoring.get
|
||||
+attachmentMonitoring
|
||||
|
||||
template(name="storageSettings")
|
||||
.storage-settings
|
||||
h3 {{_ 'attachment-storage-configuration'}}
|
||||
|
||||
.storage-config-section
|
||||
h4 {{_ 'filesystem-storage'}}
|
||||
.form-group
|
||||
label {{_ 'writable-path'}}
|
||||
input.wekan-form-control#filesystem-path(type="text" value="{{filesystemPath}}" readonly)
|
||||
small.form-text.text-muted {{_ 'filesystem-path-description'}}
|
||||
|
||||
.form-group
|
||||
label {{_ 'attachments-path'}}
|
||||
input.wekan-form-control#attachments-path(type="text" value="{{attachmentsPath}}" readonly)
|
||||
small.form-text.text-muted {{_ 'attachments-path-description'}}
|
||||
|
||||
.form-group
|
||||
label {{_ 'avatars-path'}}
|
||||
input.wekan-form-control#avatars-path(type="text" value="{{avatarsPath}}" readonly)
|
||||
small.form-text.text-muted {{_ 'avatars-path-description'}}
|
||||
|
||||
.storage-config-section
|
||||
h4 {{_ 'mongodb-gridfs-storage'}}
|
||||
.form-group
|
||||
label {{_ 'gridfs-enabled'}}
|
||||
input.wekan-form-control#gridfs-enabled(type="checkbox" checked="{{gridfsEnabled}}" disabled)
|
||||
small.form-text.text-muted {{_ 'gridfs-enabled-description'}}
|
||||
|
||||
.storage-config-section
|
||||
h4 {{_ 's3-minio-storage'}}
|
||||
.form-group
|
||||
label {{_ 's3-enabled'}}
|
||||
input.wekan-form-control#s3-enabled(type="checkbox" checked="{{s3Enabled}}" disabled)
|
||||
small.form-text.text-muted {{_ 's3-enabled-description'}}
|
||||
|
||||
.form-group
|
||||
label {{_ 's3-endpoint'}}
|
||||
input.wekan-form-control#s3-endpoint(type="text" value="{{s3Endpoint}}" readonly)
|
||||
small.form-text.text-muted {{_ 's3-endpoint-description'}}
|
||||
|
||||
.form-group
|
||||
label {{_ 's3-bucket'}}
|
||||
input.wekan-form-control#s3-bucket(type="text" value="{{s3Bucket}}" readonly)
|
||||
small.form-text.text-muted {{_ 's3-bucket-description'}}
|
||||
|
||||
.form-group
|
||||
label {{_ 's3-region'}}
|
||||
input.wekan-form-control#s3-region(type="text" value="{{s3Region}}" readonly)
|
||||
small.form-text.text-muted {{_ 's3-region-description'}}
|
||||
|
||||
.form-group
|
||||
label {{_ 's3-access-key'}}
|
||||
input.wekan-form-control#s3-access-key(type="text" placeholder="{{_ 's3-access-key-placeholder'}}" readonly)
|
||||
small.form-text.text-muted {{_ 's3-access-key-description'}}
|
||||
|
||||
.form-group
|
||||
label {{_ 's3-secret-key'}}
|
||||
input.wekan-form-control#s3-secret-key(type="password" placeholder="{{_ 's3-secret-key-placeholder'}}")
|
||||
small.form-text.text-muted {{_ 's3-secret-key-description'}}
|
||||
|
||||
.form-group
|
||||
label {{_ 's3-ssl-enabled'}}
|
||||
input.wekan-form-control#s3-ssl-enabled(type="checkbox" checked="{{s3SslEnabled}}" disabled)
|
||||
small.form-text.text-muted {{_ 's3-ssl-enabled-description'}}
|
||||
|
||||
.form-group
|
||||
label {{_ 's3-port'}}
|
||||
input.wekan-form-control#s3-port(type="number" value="{{s3Port}}" readonly)
|
||||
small.form-text.text-muted {{_ 's3-port-description'}}
|
||||
|
||||
.storage-actions
|
||||
button.js-test-s3-connection.btn.btn-secondary {{_ 'test-s3-connection'}}
|
||||
button.js-save-s3-settings.btn.btn-primary {{_ 'save-s3-settings'}}
|
||||
|
||||
template(name="attachmentMigration")
|
||||
.attachment-migration
|
||||
h3 {{_ 'attachment-migration'}}
|
||||
|
||||
.migration-controls
|
||||
.form-group
|
||||
label {{_ 'migration-batch-size'}}
|
||||
input.wekan-form-control#migration-batch-size(type="number" value="{{migrationBatchSize}}" min="1" max="100")
|
||||
small.form-text.text-muted {{_ 'migration-batch-size-description'}}
|
||||
|
||||
.form-group
|
||||
label {{_ 'migration-delay-ms'}}
|
||||
input.wekan-form-control#migration-delay-ms(type="number" value="{{migrationDelayMs}}" min="100" max="10000")
|
||||
small.form-text.text-muted {{_ 'migration-delay-ms-description'}}
|
||||
|
||||
.form-group
|
||||
label {{_ 'migration-cpu-threshold'}}
|
||||
input.wekan-form-control#migration-cpu-threshold(type="number" value="{{migrationCpuThreshold}}" min="10" max="90")
|
||||
small.form-text.text-muted {{_ 'migration-cpu-threshold-description'}}
|
||||
|
||||
.migration-actions
|
||||
.migration-buttons
|
||||
button.js-migrate-all-to-filesystem.btn.btn-primary {{_ 'migrate-all-to-filesystem'}}
|
||||
button.js-migrate-all-to-gridfs.btn.btn-primary {{_ 'migrate-all-to-gridfs'}}
|
||||
button.js-migrate-all-to-s3.btn.btn-primary {{_ 'migrate-all-to-s3'}}
|
||||
|
||||
.migration-controls
|
||||
button.js-pause-migration.btn.btn-warning {{_ 'pause-migration'}}
|
||||
button.js-resume-migration.btn.btn-success {{_ 'resume-migration'}}
|
||||
button.js-stop-migration.btn.btn-danger {{_ 'stop-migration'}}
|
||||
|
||||
.migration-progress
|
||||
h4 {{_ 'migration-progress'}}
|
||||
.progress
|
||||
.progress-bar(role="progressbar" style="width: {{migrationProgress}}%" aria-valuenow="{{migrationProgress}}" aria-valuemin="0" aria-valuemax="100")
|
||||
| {{migrationProgress}}%
|
||||
|
||||
.migration-stats
|
||||
.stat-item
|
||||
span.label {{_ 'total-attachments'}}:
|
||||
span.value {{totalAttachments}}
|
||||
.stat-item
|
||||
span.label {{_ 'migrated-attachments'}}:
|
||||
span.value {{migratedAttachments}}
|
||||
.stat-item
|
||||
span.label {{_ 'remaining-attachments'}}:
|
||||
span.value {{remainingAttachments}}
|
||||
.stat-item
|
||||
span.label {{_ 'migration-status'}}:
|
||||
span.value {{migrationStatus}}
|
||||
|
||||
.migration-log
|
||||
h4 {{_ 'migration-log'}}
|
||||
.log-container
|
||||
pre#migration-log-content {{migrationLog}}
|
||||
|
||||
template(name="attachmentMonitoring")
|
||||
.attachment-monitoring
|
||||
h3 {{_ 'attachment-monitoring'}}
|
||||
|
||||
.monitoring-stats
|
||||
.stats-grid
|
||||
.stat-card
|
||||
h5 {{_ 'total-attachments'}}
|
||||
.stat-value {{totalAttachments}}
|
||||
.stat-card
|
||||
h5 {{_ 'filesystem-attachments'}}
|
||||
.stat-value {{filesystemAttachments}}
|
||||
.stat-card
|
||||
h5 {{_ 'gridfs-attachments'}}
|
||||
.stat-value {{gridfsAttachments}}
|
||||
.stat-card
|
||||
h5 {{_ 's3-attachments'}}
|
||||
.stat-value {{s3Attachments}}
|
||||
.stat-card
|
||||
h5 {{_ 'total-size'}}
|
||||
.stat-value {{totalSize}}
|
||||
.stat-card
|
||||
h5 {{_ 'filesystem-size'}}
|
||||
.stat-value {{filesystemSize}}
|
||||
.stat-card
|
||||
h5 {{_ 'gridfs-size'}}
|
||||
.stat-value {{gridfsSize}}
|
||||
.stat-card
|
||||
h5 {{_ 's3-size'}}
|
||||
.stat-value {{s3Size}}
|
||||
|
||||
.monitoring-charts
|
||||
h4 {{_ 'storage-distribution'}}
|
||||
.chart-container
|
||||
canvas#storage-distribution-chart
|
||||
|
||||
.monitoring-actions
|
||||
button.js-refresh-monitoring.btn.btn-secondary {{_ 'refresh-monitoring'}}
|
||||
button.js-export-monitoring.btn.btn-primary {{_ 'export-monitoring'}}
|
||||
464
client/components/settings/attachmentSettings.js
Normal file
464
client/components/settings/attachmentSettings.js
Normal file
|
|
@ -0,0 +1,464 @@
|
|||
import { ReactiveCache } from '/imports/reactiveCache';
|
||||
import { TAPi18n } from '/imports/i18n';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { Session } from 'meteor/session';
|
||||
import { Tracker } from 'meteor/tracker';
|
||||
import { ReactiveVar } from 'meteor/reactive-var';
|
||||
import { BlazeComponent } from 'meteor/peerlibrary:blaze-components';
|
||||
import { Chart } from 'chart.js';
|
||||
|
||||
// Global reactive variables for attachment settings
|
||||
const attachmentSettings = {
|
||||
loading: new ReactiveVar(false),
|
||||
showStorageSettings: new ReactiveVar(false),
|
||||
showMigration: new ReactiveVar(false),
|
||||
showMonitoring: new ReactiveVar(false),
|
||||
|
||||
// Storage configuration
|
||||
filesystemPath: new ReactiveVar(''),
|
||||
attachmentsPath: new ReactiveVar(''),
|
||||
avatarsPath: new ReactiveVar(''),
|
||||
gridfsEnabled: new ReactiveVar(false),
|
||||
s3Enabled: new ReactiveVar(false),
|
||||
s3Endpoint: new ReactiveVar(''),
|
||||
s3Bucket: new ReactiveVar(''),
|
||||
s3Region: new ReactiveVar(''),
|
||||
s3SslEnabled: new ReactiveVar(false),
|
||||
s3Port: new ReactiveVar(443),
|
||||
|
||||
// Migration settings
|
||||
migrationBatchSize: new ReactiveVar(10),
|
||||
migrationDelayMs: new ReactiveVar(1000),
|
||||
migrationCpuThreshold: new ReactiveVar(70),
|
||||
migrationProgress: new ReactiveVar(0),
|
||||
migrationStatus: new ReactiveVar('idle'),
|
||||
migrationLog: new ReactiveVar(''),
|
||||
|
||||
// Monitoring data
|
||||
totalAttachments: new ReactiveVar(0),
|
||||
filesystemAttachments: new ReactiveVar(0),
|
||||
gridfsAttachments: new ReactiveVar(0),
|
||||
s3Attachments: new ReactiveVar(0),
|
||||
totalSize: new ReactiveVar(0),
|
||||
filesystemSize: new ReactiveVar(0),
|
||||
gridfsSize: new ReactiveVar(0),
|
||||
s3Size: new ReactiveVar(0),
|
||||
|
||||
// Migration state
|
||||
isMigrationRunning: new ReactiveVar(false),
|
||||
isMigrationPaused: new ReactiveVar(false),
|
||||
migrationQueue: new ReactiveVar([]),
|
||||
currentMigration: new ReactiveVar(null)
|
||||
};
|
||||
|
||||
// Main attachment settings component
|
||||
BlazeComponent.extendComponent({
|
||||
onCreated() {
|
||||
this.loading = attachmentSettings.loading;
|
||||
this.showStorageSettings = attachmentSettings.showStorageSettings;
|
||||
this.showMigration = attachmentSettings.showMigration;
|
||||
this.showMonitoring = attachmentSettings.showMonitoring;
|
||||
|
||||
// Load initial data
|
||||
this.loadStorageConfiguration();
|
||||
this.loadMigrationSettings();
|
||||
this.loadMonitoringData();
|
||||
},
|
||||
|
||||
events() {
|
||||
return [
|
||||
{
|
||||
'click a.js-attachment-storage-settings': this.switchToStorageSettings,
|
||||
'click a.js-attachment-migration': this.switchToMigration,
|
||||
'click a.js-attachment-monitoring': this.switchToMonitoring,
|
||||
}
|
||||
];
|
||||
},
|
||||
|
||||
switchToStorageSettings(event) {
|
||||
this.switchMenu(event, 'storage-settings');
|
||||
this.showStorageSettings.set(true);
|
||||
this.showMigration.set(false);
|
||||
this.showMonitoring.set(false);
|
||||
},
|
||||
|
||||
switchToMigration(event) {
|
||||
this.switchMenu(event, 'attachment-migration');
|
||||
this.showStorageSettings.set(false);
|
||||
this.showMigration.set(true);
|
||||
this.showMonitoring.set(false);
|
||||
},
|
||||
|
||||
switchToMonitoring(event) {
|
||||
this.switchMenu(event, 'attachment-monitoring');
|
||||
this.showStorageSettings.set(false);
|
||||
this.showMigration.set(false);
|
||||
this.showMonitoring.set(true);
|
||||
},
|
||||
|
||||
switchMenu(event, targetId) {
|
||||
const target = $(event.target);
|
||||
if (!target.hasClass('active')) {
|
||||
this.loading.set(true);
|
||||
|
||||
$('.side-menu li.active').removeClass('active');
|
||||
target.parent().addClass('active');
|
||||
|
||||
// Load data based on target
|
||||
if (targetId === 'storage-settings') {
|
||||
this.loadStorageConfiguration();
|
||||
} else if (targetId === 'attachment-migration') {
|
||||
this.loadMigrationSettings();
|
||||
} else if (targetId === 'attachment-monitoring') {
|
||||
this.loadMonitoringData();
|
||||
}
|
||||
|
||||
this.loading.set(false);
|
||||
}
|
||||
},
|
||||
|
||||
loadStorageConfiguration() {
|
||||
Meteor.call('getAttachmentStorageConfiguration', (error, result) => {
|
||||
if (!error && result) {
|
||||
attachmentSettings.filesystemPath.set(result.filesystemPath || '');
|
||||
attachmentSettings.attachmentsPath.set(result.attachmentsPath || '');
|
||||
attachmentSettings.avatarsPath.set(result.avatarsPath || '');
|
||||
attachmentSettings.gridfsEnabled.set(result.gridfsEnabled || false);
|
||||
attachmentSettings.s3Enabled.set(result.s3Enabled || false);
|
||||
attachmentSettings.s3Endpoint.set(result.s3Endpoint || '');
|
||||
attachmentSettings.s3Bucket.set(result.s3Bucket || '');
|
||||
attachmentSettings.s3Region.set(result.s3Region || '');
|
||||
attachmentSettings.s3SslEnabled.set(result.s3SslEnabled || false);
|
||||
attachmentSettings.s3Port.set(result.s3Port || 443);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
loadMigrationSettings() {
|
||||
Meteor.call('getAttachmentMigrationSettings', (error, result) => {
|
||||
if (!error && result) {
|
||||
attachmentSettings.migrationBatchSize.set(result.batchSize || 10);
|
||||
attachmentSettings.migrationDelayMs.set(result.delayMs || 1000);
|
||||
attachmentSettings.migrationCpuThreshold.set(result.cpuThreshold || 70);
|
||||
attachmentSettings.migrationStatus.set(result.status || 'idle');
|
||||
attachmentSettings.migrationProgress.set(result.progress || 0);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
loadMonitoringData() {
|
||||
Meteor.call('getAttachmentMonitoringData', (error, result) => {
|
||||
if (!error && result) {
|
||||
attachmentSettings.totalAttachments.set(result.totalAttachments || 0);
|
||||
attachmentSettings.filesystemAttachments.set(result.filesystemAttachments || 0);
|
||||
attachmentSettings.gridfsAttachments.set(result.gridfsAttachments || 0);
|
||||
attachmentSettings.s3Attachments.set(result.s3Attachments || 0);
|
||||
attachmentSettings.totalSize.set(result.totalSize || 0);
|
||||
attachmentSettings.filesystemSize.set(result.filesystemSize || 0);
|
||||
attachmentSettings.gridfsSize.set(result.gridfsSize || 0);
|
||||
attachmentSettings.s3Size.set(result.s3Size || 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
}).register('attachmentSettings');
|
||||
|
||||
// Storage settings component
|
||||
BlazeComponent.extendComponent({
|
||||
onCreated() {
|
||||
this.filesystemPath = attachmentSettings.filesystemPath;
|
||||
this.attachmentsPath = attachmentSettings.attachmentsPath;
|
||||
this.avatarsPath = attachmentSettings.avatarsPath;
|
||||
this.gridfsEnabled = attachmentSettings.gridfsEnabled;
|
||||
this.s3Enabled = attachmentSettings.s3Enabled;
|
||||
this.s3Endpoint = attachmentSettings.s3Endpoint;
|
||||
this.s3Bucket = attachmentSettings.s3Bucket;
|
||||
this.s3Region = attachmentSettings.s3Region;
|
||||
this.s3SslEnabled = attachmentSettings.s3SslEnabled;
|
||||
this.s3Port = attachmentSettings.s3Port;
|
||||
},
|
||||
|
||||
events() {
|
||||
return [
|
||||
{
|
||||
'click button.js-test-s3-connection': this.testS3Connection,
|
||||
'click button.js-save-s3-settings': this.saveS3Settings,
|
||||
'change input#s3-secret-key': this.updateS3SecretKey
|
||||
}
|
||||
];
|
||||
},
|
||||
|
||||
testS3Connection() {
|
||||
const secretKey = $('#s3-secret-key').val();
|
||||
if (!secretKey) {
|
||||
alert(TAPi18n.__('s3-secret-key-required'));
|
||||
return;
|
||||
}
|
||||
|
||||
Meteor.call('testS3Connection', { secretKey }, (error, result) => {
|
||||
if (error) {
|
||||
alert(TAPi18n.__('s3-connection-failed') + ': ' + error.reason);
|
||||
} else {
|
||||
alert(TAPi18n.__('s3-connection-success'));
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
saveS3Settings() {
|
||||
const secretKey = $('#s3-secret-key').val();
|
||||
if (!secretKey) {
|
||||
alert(TAPi18n.__('s3-secret-key-required'));
|
||||
return;
|
||||
}
|
||||
|
||||
Meteor.call('saveS3Settings', { secretKey }, (error, result) => {
|
||||
if (error) {
|
||||
alert(TAPi18n.__('s3-settings-save-failed') + ': ' + error.reason);
|
||||
} else {
|
||||
alert(TAPi18n.__('s3-settings-saved'));
|
||||
$('#s3-secret-key').val(''); // Clear the password field
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
updateS3SecretKey(event) {
|
||||
// This method can be used to validate the secret key format
|
||||
const secretKey = event.target.value;
|
||||
// Add validation logic here if needed
|
||||
}
|
||||
}).register('storageSettings');
|
||||
|
||||
// Migration component
|
||||
BlazeComponent.extendComponent({
|
||||
onCreated() {
|
||||
this.migrationBatchSize = attachmentSettings.migrationBatchSize;
|
||||
this.migrationDelayMs = attachmentSettings.migrationDelayMs;
|
||||
this.migrationCpuThreshold = attachmentSettings.migrationCpuThreshold;
|
||||
this.migrationProgress = attachmentSettings.migrationProgress;
|
||||
this.migrationStatus = attachmentSettings.migrationStatus;
|
||||
this.migrationLog = attachmentSettings.migrationLog;
|
||||
this.isMigrationRunning = attachmentSettings.isMigrationRunning;
|
||||
this.isMigrationPaused = attachmentSettings.isMigrationPaused;
|
||||
|
||||
// Subscribe to migration updates
|
||||
this.subscription = Meteor.subscribe('attachmentMigrationStatus');
|
||||
|
||||
// Set up reactive updates
|
||||
this.autorun(() => {
|
||||
const status = attachmentSettings.migrationStatus.get();
|
||||
if (status === 'running') {
|
||||
this.isMigrationRunning.set(true);
|
||||
} else {
|
||||
this.isMigrationRunning.set(false);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
onDestroyed() {
|
||||
if (this.subscription) {
|
||||
this.subscription.stop();
|
||||
}
|
||||
},
|
||||
|
||||
events() {
|
||||
return [
|
||||
{
|
||||
'click button.js-migrate-all-to-filesystem': () => this.startMigration('filesystem'),
|
||||
'click button.js-migrate-all-to-gridfs': () => this.startMigration('gridfs'),
|
||||
'click button.js-migrate-all-to-s3': () => this.startMigration('s3'),
|
||||
'click button.js-pause-migration': this.pauseMigration,
|
||||
'click button.js-resume-migration': this.resumeMigration,
|
||||
'click button.js-stop-migration': this.stopMigration,
|
||||
'change input#migration-batch-size': this.updateBatchSize,
|
||||
'change input#migration-delay-ms': this.updateDelayMs,
|
||||
'change input#migration-cpu-threshold': this.updateCpuThreshold
|
||||
}
|
||||
];
|
||||
},
|
||||
|
||||
startMigration(targetStorage) {
|
||||
const batchSize = parseInt($('#migration-batch-size').val()) || 10;
|
||||
const delayMs = parseInt($('#migration-delay-ms').val()) || 1000;
|
||||
const cpuThreshold = parseInt($('#migration-cpu-threshold').val()) || 70;
|
||||
|
||||
Meteor.call('startAttachmentMigration', {
|
||||
targetStorage,
|
||||
batchSize,
|
||||
delayMs,
|
||||
cpuThreshold
|
||||
}, (error, result) => {
|
||||
if (error) {
|
||||
alert(TAPi18n.__('migration-start-failed') + ': ' + error.reason);
|
||||
} else {
|
||||
this.addToLog(TAPi18n.__('migration-started') + ': ' + targetStorage);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
pauseMigration() {
|
||||
Meteor.call('pauseAttachmentMigration', (error, result) => {
|
||||
if (error) {
|
||||
alert(TAPi18n.__('migration-pause-failed') + ': ' + error.reason);
|
||||
} else {
|
||||
this.addToLog(TAPi18n.__('migration-paused'));
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
resumeMigration() {
|
||||
Meteor.call('resumeAttachmentMigration', (error, result) => {
|
||||
if (error) {
|
||||
alert(TAPi18n.__('migration-resume-failed') + ': ' + error.reason);
|
||||
} else {
|
||||
this.addToLog(TAPi18n.__('migration-resumed'));
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
stopMigration() {
|
||||
if (confirm(TAPi18n.__('migration-stop-confirm'))) {
|
||||
Meteor.call('stopAttachmentMigration', (error, result) => {
|
||||
if (error) {
|
||||
alert(TAPi18n.__('migration-stop-failed') + ': ' + error.reason);
|
||||
} else {
|
||||
this.addToLog(TAPi18n.__('migration-stopped'));
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
updateBatchSize(event) {
|
||||
const value = parseInt(event.target.value);
|
||||
if (value >= 1 && value <= 100) {
|
||||
attachmentSettings.migrationBatchSize.set(value);
|
||||
}
|
||||
},
|
||||
|
||||
updateDelayMs(event) {
|
||||
const value = parseInt(event.target.value);
|
||||
if (value >= 100 && value <= 10000) {
|
||||
attachmentSettings.migrationDelayMs.set(value);
|
||||
}
|
||||
},
|
||||
|
||||
updateCpuThreshold(event) {
|
||||
const value = parseInt(event.target.value);
|
||||
if (value >= 10 && value <= 90) {
|
||||
attachmentSettings.migrationCpuThreshold.set(value);
|
||||
}
|
||||
},
|
||||
|
||||
addToLog(message) {
|
||||
const timestamp = new Date().toISOString();
|
||||
const currentLog = attachmentSettings.migrationLog.get();
|
||||
const newLog = `[${timestamp}] ${message}\n${currentLog}`;
|
||||
attachmentSettings.migrationLog.set(newLog);
|
||||
}
|
||||
}).register('attachmentMigration');
|
||||
|
||||
// Monitoring component
|
||||
BlazeComponent.extendComponent({
|
||||
onCreated() {
|
||||
this.totalAttachments = attachmentSettings.totalAttachments;
|
||||
this.filesystemAttachments = attachmentSettings.filesystemAttachments;
|
||||
this.gridfsAttachments = attachmentSettings.gridfsAttachments;
|
||||
this.s3Attachments = attachmentSettings.s3Attachments;
|
||||
this.totalSize = attachmentSettings.totalSize;
|
||||
this.filesystemSize = attachmentSettings.filesystemSize;
|
||||
this.gridfsSize = attachmentSettings.gridfsSize;
|
||||
this.s3Size = attachmentSettings.s3Size;
|
||||
|
||||
// Subscribe to monitoring updates
|
||||
this.subscription = Meteor.subscribe('attachmentMonitoringData');
|
||||
|
||||
// Set up chart
|
||||
this.autorun(() => {
|
||||
this.updateChart();
|
||||
});
|
||||
},
|
||||
|
||||
onDestroyed() {
|
||||
if (this.subscription) {
|
||||
this.subscription.stop();
|
||||
}
|
||||
},
|
||||
|
||||
events() {
|
||||
return [
|
||||
{
|
||||
'click button.js-refresh-monitoring': this.refreshMonitoring,
|
||||
'click button.js-export-monitoring': this.exportMonitoring
|
||||
}
|
||||
];
|
||||
},
|
||||
|
||||
refreshMonitoring() {
|
||||
Meteor.call('refreshAttachmentMonitoringData', (error, result) => {
|
||||
if (error) {
|
||||
alert(TAPi18n.__('monitoring-refresh-failed') + ': ' + error.reason);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
exportMonitoring() {
|
||||
Meteor.call('exportAttachmentMonitoringData', (error, result) => {
|
||||
if (error) {
|
||||
alert(TAPi18n.__('monitoring-export-failed') + ': ' + error.reason);
|
||||
} else {
|
||||
// Download the exported data
|
||||
const blob = new Blob([JSON.stringify(result, null, 2)], { type: 'application/json' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = 'wekan-attachment-monitoring.json';
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
updateChart() {
|
||||
const ctx = document.getElementById('storage-distribution-chart');
|
||||
if (!ctx) return;
|
||||
|
||||
const filesystemCount = this.filesystemAttachments.get();
|
||||
const gridfsCount = this.gridfsAttachments.get();
|
||||
const s3Count = this.s3Attachments.get();
|
||||
|
||||
if (this.chart) {
|
||||
this.chart.destroy();
|
||||
}
|
||||
|
||||
this.chart = new Chart(ctx, {
|
||||
type: 'doughnut',
|
||||
data: {
|
||||
labels: [
|
||||
TAPi18n.__('filesystem-storage'),
|
||||
TAPi18n.__('gridfs-storage'),
|
||||
TAPi18n.__('s3-storage')
|
||||
],
|
||||
datasets: [{
|
||||
data: [filesystemCount, gridfsCount, s3Count],
|
||||
backgroundColor: [
|
||||
'#28a745',
|
||||
'#007bff',
|
||||
'#ffc107'
|
||||
]
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'bottom'
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}).register('attachmentMonitoring');
|
||||
|
||||
// Export the attachment settings for use in other components
|
||||
export { attachmentSettings };
|
||||
|
|
@ -42,6 +42,10 @@ template(name="setting")
|
|||
a.js-setting-menu(data-id="webhook-setting")
|
||||
i.fa.fa-globe
|
||||
| {{_ 'global-webhook'}}
|
||||
li
|
||||
a.js-setting-menu(data-id="attachment-settings")
|
||||
i.fa.fa-paperclip
|
||||
| {{_ 'attachment-settings'}}
|
||||
.main-body
|
||||
if loading.get
|
||||
+spinner
|
||||
|
|
@ -62,6 +66,8 @@ template(name="setting")
|
|||
+layoutSettings
|
||||
else if webhookSetting.get
|
||||
+webhookSettings
|
||||
else if attachmentSettings.get
|
||||
+attachmentSettings
|
||||
|
||||
template(name="webhookSettings")
|
||||
span
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ BlazeComponent.extendComponent({
|
|||
this.accessibilitySetting = new ReactiveVar(false);
|
||||
this.layoutSetting = new ReactiveVar(false);
|
||||
this.webhookSetting = new ReactiveVar(false);
|
||||
this.attachmentSettings = new ReactiveVar(false);
|
||||
|
||||
Meteor.subscribe('setting');
|
||||
Meteor.subscribe('mailServer');
|
||||
|
|
@ -113,6 +114,7 @@ BlazeComponent.extendComponent({
|
|||
this.accessibilitySetting.set('accessibility-setting' === targetID);
|
||||
this.layoutSetting.set('layout-setting' === targetID);
|
||||
this.webhookSetting.set('webhook-setting' === targetID);
|
||||
this.attachmentSettings.set('attachment-settings' === targetID);
|
||||
this.tableVisibilityModeSetting.set('tableVisibilityMode-setting' === targetID);
|
||||
}
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue