mirror of
https://github.com/wekan/wekan.git
synced 2025-12-16 15:30:13 +01:00
Fixed Admin Panel Settings menus Attachments and Cron.
Thanks to xet7 !
This commit is contained in:
parent
e0013b9b63
commit
7bb1e24bda
8 changed files with 485 additions and 1302 deletions
|
|
@ -15,6 +15,3 @@ import '/client/components/migrationProgress';
|
|||
|
||||
// Import cron settings
|
||||
import '/client/components/settings/cronSettings';
|
||||
|
||||
// Import settings tabs CSS
|
||||
import '/client/components/settings/settingsTabs.css';
|
||||
|
|
|
|||
|
|
@ -1,29 +1,74 @@
|
|||
template(name="attachmentSettings")
|
||||
.attachment-settings-content
|
||||
.settings-tabs
|
||||
ul.tab-nav
|
||||
li(class="{{#if showStorageSettings}}active{{/if}}")
|
||||
a.js-attachment-storage-settings(data-id="storage-settings")
|
||||
i.fa.fa-cog
|
||||
| {{_ 'attachment-storage-settings'}}
|
||||
li(class="{{#if showMigration}}active{{/if}}")
|
||||
a.js-attachment-migration(data-id="attachment-migration")
|
||||
i.fa.fa-arrow-right
|
||||
| {{_ 'attachment-migration'}}
|
||||
li(class="{{#if showMonitoring}}active{{/if}}")
|
||||
a.js-attachment-monitoring(data-id="attachment-monitoring")
|
||||
i.fa.fa-chart-line
|
||||
| {{_ 'attachment-monitoring'}}
|
||||
|
||||
.tab-content
|
||||
if loading
|
||||
+spinner
|
||||
else if showStorageSettings
|
||||
+storageSettings
|
||||
else if showMigration
|
||||
+attachmentMigration
|
||||
else if showMonitoring
|
||||
+attachmentMonitoring
|
||||
ul#attachment-setting.setting-detail
|
||||
li
|
||||
h3 {{_ 'attachment-storage-configuration'}}
|
||||
.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'}}
|
||||
|
||||
li
|
||||
h3 {{_ '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'}}
|
||||
|
||||
li
|
||||
h3 {{_ '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'}}
|
||||
|
||||
.form-group
|
||||
button.js-test-s3-connection.btn.btn-secondary {{_ 'test-s3-connection'}}
|
||||
button.js-save-s3-settings.btn.btn-primary {{_ 'save-s3-settings'}}
|
||||
|
||||
template(name="storageSettings")
|
||||
.storage-settings
|
||||
|
|
|
|||
|
|
@ -1,504 +0,0 @@
|
|||
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;
|
||||
|
||||
// Set default sub-menu state
|
||||
this.showStorageSettings.set(true);
|
||||
this.showMigration.set(false);
|
||||
this.showMonitoring.set(false);
|
||||
|
||||
// Load initial data
|
||||
this.loadStorageConfiguration();
|
||||
this.loadMigrationSettings();
|
||||
this.loadMonitoringData();
|
||||
},
|
||||
|
||||
// Template helpers for this component
|
||||
loading() {
|
||||
return this.loading.get();
|
||||
},
|
||||
showStorageSettings() {
|
||||
return this.showStorageSettings.get();
|
||||
},
|
||||
showMigration() {
|
||||
return this.showMigration.get();
|
||||
},
|
||||
showMonitoring() {
|
||||
return this.showMonitoring.get();
|
||||
},
|
||||
|
||||
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');
|
||||
|
||||
// Template helpers for attachmentSettings
|
||||
Template.attachmentSettings.helpers({
|
||||
loading() {
|
||||
const instance = Template.instance();
|
||||
return instance.loading && instance.loading.get();
|
||||
},
|
||||
showStorageSettings() {
|
||||
const instance = Template.instance();
|
||||
return instance.showStorageSettings && instance.showStorageSettings.get();
|
||||
},
|
||||
showMigration() {
|
||||
const instance = Template.instance();
|
||||
return instance.showMigration && instance.showMigration.get();
|
||||
},
|
||||
showMonitoring() {
|
||||
const instance = Template.instance();
|
||||
return instance.showMonitoring && instance.showMonitoring.get();
|
||||
},
|
||||
});
|
||||
|
||||
// Export the attachment settings for use in other components
|
||||
export { attachmentSettings };
|
||||
|
|
@ -1,35 +1,47 @@
|
|||
template(name="cronSettings")
|
||||
.cron-settings-content
|
||||
.settings-tabs
|
||||
ul.tab-nav
|
||||
li(class="{{#if showMigrations}}active{{/if}}")
|
||||
a.js-cron-migrations(data-id="cron-migrations")
|
||||
i.fa.fa-database
|
||||
| {{_ 'cron-migrations'}}
|
||||
li(class="{{#if showBoardOperations}}active{{/if}}")
|
||||
a.js-cron-board-operations(data-id="cron-board-operations")
|
||||
i.fa.fa-tasks
|
||||
| {{_ 'board-operations'}}
|
||||
li(class="{{#if showJobs}}active{{/if}}")
|
||||
a.js-cron-jobs(data-id="cron-jobs")
|
||||
i.fa.fa-clock-o
|
||||
| {{_ 'cron-jobs'}}
|
||||
li(class="{{#if showAddJob}}active{{/if}}")
|
||||
a.js-cron-add(data-id="cron-add")
|
||||
i.fa.fa-plus
|
||||
| {{_ 'add-cron-job'}}
|
||||
ul#cron-setting.setting-detail
|
||||
li
|
||||
h3 {{_ 'cron-migrations'}}
|
||||
.form-group
|
||||
label {{_ 'migration-status'}}
|
||||
.status-indicator
|
||||
span.status-label {{_ 'status'}}:
|
||||
span.status-value {{migrationStatus}}
|
||||
.progress-section
|
||||
.progress
|
||||
.progress-bar(role="progressbar" style="width: {{migrationProgress}}%" aria-valuenow="{{migrationProgress}}" aria-valuemin="0" aria-valuemax="100")
|
||||
| {{migrationProgress}}%
|
||||
.progress-text
|
||||
| {{migrationProgress}}% {{_ 'complete'}}
|
||||
|
||||
.form-group
|
||||
button.js-start-all-migrations.btn.btn-primary {{_ 'start-all-migrations'}}
|
||||
button.js-pause-all-migrations.btn.btn-warning {{_ 'pause-all-migrations'}}
|
||||
button.js-stop-all-migrations.btn.btn-danger {{_ 'stop-all-migrations'}}
|
||||
|
||||
.tab-content
|
||||
if loading
|
||||
+spinner
|
||||
else if showMigrations
|
||||
+cronMigrations
|
||||
else if showBoardOperations
|
||||
+cronBoardOperations
|
||||
else if showJobs
|
||||
+cronJobs
|
||||
else if showAddJob
|
||||
+cronAddJob
|
||||
li
|
||||
h3 {{_ 'board-operations'}}
|
||||
.form-group
|
||||
label {{_ 'scheduled-board-operations'}}
|
||||
button.js-schedule-board-cleanup.btn.btn-primary {{_ 'schedule-board-cleanup'}}
|
||||
button.js-schedule-board-archive.btn.btn-warning {{_ 'schedule-board-archive'}}
|
||||
button.js-schedule-board-backup.btn.btn-info {{_ 'schedule-board-backup'}}
|
||||
|
||||
li
|
||||
h3 {{_ 'cron-jobs'}}
|
||||
.form-group
|
||||
label {{_ 'active-cron-jobs'}}
|
||||
each cronJobs
|
||||
.job-item
|
||||
.job-info
|
||||
.job-name {{name}}
|
||||
.job-schedule {{schedule}}
|
||||
.job-description {{description}}
|
||||
.job-actions
|
||||
button.js-pause-job.btn.btn-sm.btn-warning(data-job-id="{{_id}}") {{_ 'pause'}}
|
||||
button.js-delete-job.btn.btn-sm.btn-danger(data-job-id="{{_id}}") {{_ 'delete'}}
|
||||
.add-job-section
|
||||
button.js-add-cron-job.btn.btn-success {{_ 'add-cron-job'}}
|
||||
|
||||
template(name="cronMigrations")
|
||||
.cron-migrations
|
||||
|
|
@ -51,7 +63,7 @@ template(name="cronMigrations")
|
|||
.migration-progress
|
||||
.progress-overview
|
||||
.progress-bar
|
||||
.progress-fill(style="width: {{migrationProgress}}%")
|
||||
.progress-fill(style="width: {{migrationProgress}}%")
|
||||
.progress-text {{migrationProgress}}%
|
||||
.progress-label {{_ 'overall-progress'}}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,552 +0,0 @@
|
|||
import { Template } from 'meteor/templating';
|
||||
import { ReactiveVar } from 'meteor/reactive-var';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { TAPi18n } from '/imports/i18n';
|
||||
|
||||
// Reactive variables for cron settings
|
||||
const migrationProgress = new ReactiveVar(0);
|
||||
const migrationStatus = new ReactiveVar('');
|
||||
const migrationCurrentStep = new ReactiveVar('');
|
||||
const migrationSteps = new ReactiveVar([]);
|
||||
const isMigrating = new ReactiveVar(false);
|
||||
const cronJobs = new ReactiveVar([]);
|
||||
|
||||
Template.cronSettings.onCreated(function() {
|
||||
this.loading = new ReactiveVar(true);
|
||||
this.showMigrations = new ReactiveVar(true);
|
||||
this.showBoardOperations = new ReactiveVar(false);
|
||||
this.showJobs = new ReactiveVar(false);
|
||||
this.showAddJob = new ReactiveVar(false);
|
||||
|
||||
// Board operations pagination
|
||||
this.currentPage = new ReactiveVar(1);
|
||||
this.pageSize = new ReactiveVar(20);
|
||||
this.searchTerm = new ReactiveVar('');
|
||||
this.boardOperations = new ReactiveVar([]);
|
||||
this.operationStats = new ReactiveVar({});
|
||||
this.pagination = new ReactiveVar({});
|
||||
this.queueStats = new ReactiveVar({});
|
||||
this.systemResources = new ReactiveVar({});
|
||||
this.boardMigrationStats = new ReactiveVar({});
|
||||
|
||||
// Load initial data
|
||||
loadCronData(this);
|
||||
});
|
||||
|
||||
Template.cronSettings.helpers({
|
||||
loading() {
|
||||
const instance = Template.instance();
|
||||
return instance && instance.loading ? instance.loading.get() : true;
|
||||
},
|
||||
|
||||
showMigrations() {
|
||||
const instance = Template.instance();
|
||||
return instance && instance.showMigrations ? instance.showMigrations.get() : true;
|
||||
},
|
||||
|
||||
showBoardOperations() {
|
||||
const instance = Template.instance();
|
||||
return instance && instance.showBoardOperations ? instance.showBoardOperations.get() : false;
|
||||
},
|
||||
|
||||
showJobs() {
|
||||
const instance = Template.instance();
|
||||
return instance && instance.showJobs ? instance.showJobs.get() : false;
|
||||
},
|
||||
|
||||
showAddJob() {
|
||||
const instance = Template.instance();
|
||||
return instance && instance.showAddJob ? instance.showAddJob.get() : false;
|
||||
},
|
||||
|
||||
migrationProgress() {
|
||||
return migrationProgress.get();
|
||||
},
|
||||
|
||||
migrationStatus() {
|
||||
return migrationStatus.get();
|
||||
},
|
||||
|
||||
migrationCurrentStep() {
|
||||
return migrationCurrentStep.get();
|
||||
},
|
||||
|
||||
migrationSteps() {
|
||||
const steps = migrationSteps.get();
|
||||
const currentStep = migrationCurrentStep.get();
|
||||
|
||||
return steps.map(step => ({
|
||||
...step,
|
||||
isCurrentStep: step.name === currentStep
|
||||
}));
|
||||
},
|
||||
|
||||
cronJobs() {
|
||||
return cronJobs.get();
|
||||
},
|
||||
|
||||
formatDate(date) {
|
||||
if (!date) return '-';
|
||||
return new Date(date).toLocaleString();
|
||||
},
|
||||
|
||||
boardOperations() {
|
||||
const instance = Template.instance();
|
||||
return instance && instance.boardOperations ? instance.boardOperations.get() : [];
|
||||
},
|
||||
|
||||
operationStats() {
|
||||
const instance = Template.instance();
|
||||
return instance && instance.operationStats ? instance.operationStats.get() : {};
|
||||
},
|
||||
|
||||
pagination() {
|
||||
const instance = Template.instance();
|
||||
return instance && instance.pagination ? instance.pagination.get() : {};
|
||||
},
|
||||
|
||||
queueStats() {
|
||||
const instance = Template.instance();
|
||||
return instance && instance.queueStats ? instance.queueStats.get() : {};
|
||||
},
|
||||
|
||||
systemResources() {
|
||||
const instance = Template.instance();
|
||||
return instance && instance.systemResources ? instance.systemResources.get() : {};
|
||||
},
|
||||
|
||||
boardMigrationStats() {
|
||||
const instance = Template.instance();
|
||||
return instance && instance.boardMigrationStats ? instance.boardMigrationStats.get() : {};
|
||||
},
|
||||
|
||||
formatDateTime(date) {
|
||||
if (!date) return '-';
|
||||
return new Date(date).toLocaleString();
|
||||
},
|
||||
|
||||
formatDuration(startTime, endTime) {
|
||||
if (!startTime) return '-';
|
||||
const start = new Date(startTime);
|
||||
const end = endTime ? new Date(endTime) : new Date();
|
||||
const diffMs = end - start;
|
||||
const diffMins = Math.floor(diffMs / 60000);
|
||||
const diffSecs = Math.floor((diffMs % 60000) / 1000);
|
||||
|
||||
if (diffMins > 0) {
|
||||
return `${diffMins}m ${diffSecs}s`;
|
||||
} else {
|
||||
return `${diffSecs}s`;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Template.cronSettings.switchMenu = function(event, targetID) {
|
||||
const instance = Template.instance();
|
||||
|
||||
// Reset all tabs
|
||||
instance.showMigrations.set(false);
|
||||
instance.showBoardOperations.set(false);
|
||||
instance.showJobs.set(false);
|
||||
instance.showAddJob.set(false);
|
||||
|
||||
// Set the selected tab
|
||||
if (targetID === 'cron-migrations') {
|
||||
instance.showMigrations.set(true);
|
||||
} else if (targetID === 'cron-board-operations') {
|
||||
instance.showBoardOperations.set(true);
|
||||
} else if (targetID === 'cron-jobs') {
|
||||
instance.showJobs.set(true);
|
||||
} else if (targetID === 'cron-add') {
|
||||
instance.showAddJob.set(true);
|
||||
}
|
||||
};
|
||||
|
||||
Template.cronSettings.events({
|
||||
'click .js-cron-migrations'(event) {
|
||||
event.preventDefault();
|
||||
const instance = Template.instance();
|
||||
instance.showMigrations.set(true);
|
||||
instance.showJobs.set(false);
|
||||
instance.showAddJob.set(false);
|
||||
},
|
||||
|
||||
'click .js-cron-board-operations'(event) {
|
||||
event.preventDefault();
|
||||
const instance = Template.instance();
|
||||
instance.showMigrations.set(false);
|
||||
instance.showBoardOperations.set(true);
|
||||
instance.showJobs.set(false);
|
||||
instance.showAddJob.set(false);
|
||||
loadBoardOperations(instance);
|
||||
},
|
||||
|
||||
'click .js-cron-jobs'(event) {
|
||||
event.preventDefault();
|
||||
const instance = Template.instance();
|
||||
instance.showMigrations.set(false);
|
||||
instance.showBoardOperations.set(false);
|
||||
instance.showJobs.set(true);
|
||||
instance.showAddJob.set(false);
|
||||
loadCronJobs(instance);
|
||||
},
|
||||
|
||||
'click .js-cron-add'(event) {
|
||||
event.preventDefault();
|
||||
const instance = Template.instance();
|
||||
instance.showMigrations.set(false);
|
||||
instance.showJobs.set(false);
|
||||
instance.showAddJob.set(true);
|
||||
},
|
||||
|
||||
'click .js-start-all-migrations'(event) {
|
||||
event.preventDefault();
|
||||
Meteor.call('cron.startAllMigrations', (error, result) => {
|
||||
if (error) {
|
||||
console.error('Failed to start migrations:', error);
|
||||
alert('Failed to start migrations: ' + error.message);
|
||||
} else {
|
||||
// Migrations started successfully
|
||||
pollMigrationProgress(Template.instance());
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
'click .js-pause-all-migrations'(event) {
|
||||
event.preventDefault();
|
||||
// Pause all migration cron jobs
|
||||
const jobs = cronJobs.get();
|
||||
jobs.forEach(job => {
|
||||
if (job.name.startsWith('migration_')) {
|
||||
Meteor.call('cron.pauseJob', job.name);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
'click .js-stop-all-migrations'(event) {
|
||||
event.preventDefault();
|
||||
// Stop all migration cron jobs
|
||||
const jobs = cronJobs.get();
|
||||
jobs.forEach(job => {
|
||||
if (job.name.startsWith('migration_')) {
|
||||
Meteor.call('cron.stopJob', job.name);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
'click .js-refresh-jobs'(event) {
|
||||
event.preventDefault();
|
||||
loadCronJobs(Template.instance());
|
||||
},
|
||||
|
||||
'click .js-start-job'(event) {
|
||||
event.preventDefault();
|
||||
const jobName = $(event.currentTarget).data('job');
|
||||
Meteor.call('cron.startJob', jobName, (error, result) => {
|
||||
if (error) {
|
||||
console.error('Failed to start job:', error);
|
||||
alert('Failed to start job: ' + error.message);
|
||||
} else {
|
||||
console.log('Job started successfully');
|
||||
loadCronJobs(Template.instance());
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
'click .js-pause-job'(event) {
|
||||
event.preventDefault();
|
||||
const jobName = $(event.currentTarget).data('job');
|
||||
Meteor.call('cron.pauseJob', jobName, (error, result) => {
|
||||
if (error) {
|
||||
console.error('Failed to pause job:', error);
|
||||
alert('Failed to pause job: ' + error.message);
|
||||
} else {
|
||||
console.log('Job paused successfully');
|
||||
loadCronJobs(Template.instance());
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
'click .js-stop-job'(event) {
|
||||
event.preventDefault();
|
||||
const jobName = $(event.currentTarget).data('job');
|
||||
Meteor.call('cron.stopJob', jobName, (error, result) => {
|
||||
if (error) {
|
||||
console.error('Failed to stop job:', error);
|
||||
alert('Failed to stop job: ' + error.message);
|
||||
} else {
|
||||
console.log('Job stopped successfully');
|
||||
loadCronJobs(Template.instance());
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
'click .js-remove-job'(event) {
|
||||
event.preventDefault();
|
||||
const jobName = $(event.currentTarget).data('job');
|
||||
if (confirm('Are you sure you want to remove this job?')) {
|
||||
Meteor.call('cron.removeJob', jobName, (error, result) => {
|
||||
if (error) {
|
||||
console.error('Failed to remove job:', error);
|
||||
alert('Failed to remove job: ' + error.message);
|
||||
} else {
|
||||
console.log('Job removed successfully');
|
||||
loadCronJobs(Template.instance());
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
'submit .js-add-cron-job-form'(event) {
|
||||
event.preventDefault();
|
||||
const form = event.currentTarget;
|
||||
const formData = new FormData(form);
|
||||
|
||||
const jobData = {
|
||||
name: formData.get('name'),
|
||||
description: formData.get('description'),
|
||||
schedule: formData.get('schedule'),
|
||||
weight: parseInt(formData.get('weight'))
|
||||
};
|
||||
|
||||
Meteor.call('cron.addJob', jobData, (error, result) => {
|
||||
if (error) {
|
||||
console.error('Failed to add job:', error);
|
||||
alert('Failed to add job: ' + error.message);
|
||||
} else {
|
||||
console.log('Job added successfully');
|
||||
form.reset();
|
||||
Template.instance().showJobs.set(true);
|
||||
Template.instance().showAddJob.set(false);
|
||||
loadCronJobs(Template.instance());
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
'click .js-cancel-add-job'(event) {
|
||||
event.preventDefault();
|
||||
const instance = Template.instance();
|
||||
instance.showJobs.set(true);
|
||||
instance.showAddJob.set(false);
|
||||
},
|
||||
|
||||
'click .js-refresh-board-operations'(event) {
|
||||
event.preventDefault();
|
||||
loadBoardOperations(Template.instance());
|
||||
},
|
||||
|
||||
'click .js-start-test-operation'(event) {
|
||||
event.preventDefault();
|
||||
const testBoardId = 'test-board-' + Date.now();
|
||||
const operationData = {
|
||||
sourceBoardId: 'source-board',
|
||||
targetBoardId: 'target-board',
|
||||
copyOptions: { includeCards: true, includeAttachments: true }
|
||||
};
|
||||
|
||||
Meteor.call('cron.startBoardOperation', testBoardId, 'copy_board', operationData, (error, result) => {
|
||||
if (error) {
|
||||
console.error('Failed to start test operation:', error);
|
||||
alert('Failed to start test operation: ' + error.message);
|
||||
} else {
|
||||
console.log('Test operation started:', result);
|
||||
loadBoardOperations(Template.instance());
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
'input .js-search-board-operations'(event) {
|
||||
const searchTerm = $(event.currentTarget).val();
|
||||
const instance = Template.instance();
|
||||
instance.searchTerm.set(searchTerm);
|
||||
instance.currentPage.set(1);
|
||||
loadBoardOperations(instance);
|
||||
},
|
||||
|
||||
'click .js-prev-page'(event) {
|
||||
event.preventDefault();
|
||||
const instance = Template.instance();
|
||||
const currentPage = instance.currentPage.get();
|
||||
if (currentPage > 1) {
|
||||
instance.currentPage.set(currentPage - 1);
|
||||
loadBoardOperations(instance);
|
||||
}
|
||||
},
|
||||
|
||||
'click .js-next-page'(event) {
|
||||
event.preventDefault();
|
||||
const instance = Template.instance();
|
||||
const currentPage = instance.currentPage.get();
|
||||
const pagination = instance.pagination.get();
|
||||
if (currentPage < pagination.totalPages) {
|
||||
instance.currentPage.set(currentPage + 1);
|
||||
loadBoardOperations(instance);
|
||||
}
|
||||
},
|
||||
|
||||
'click .js-pause-operation'(event) {
|
||||
event.preventDefault();
|
||||
const operationId = $(event.currentTarget).data('operation');
|
||||
// Implementation for pausing operation
|
||||
console.log('Pause operation:', operationId);
|
||||
},
|
||||
|
||||
'click .js-resume-operation'(event) {
|
||||
event.preventDefault();
|
||||
const operationId = $(event.currentTarget).data('operation');
|
||||
// Implementation for resuming operation
|
||||
console.log('Resume operation:', operationId);
|
||||
},
|
||||
|
||||
'click .js-stop-operation'(event) {
|
||||
event.preventDefault();
|
||||
const operationId = $(event.currentTarget).data('operation');
|
||||
if (confirm('Are you sure you want to stop this operation?')) {
|
||||
// Implementation for stopping operation
|
||||
console.log('Stop operation:', operationId);
|
||||
}
|
||||
},
|
||||
|
||||
'click .js-view-details'(event) {
|
||||
event.preventDefault();
|
||||
const operationId = $(event.currentTarget).data('operation');
|
||||
// Implementation for viewing operation details
|
||||
console.log('View details for operation:', operationId);
|
||||
},
|
||||
|
||||
'click .js-force-board-scan'(event) {
|
||||
event.preventDefault();
|
||||
Meteor.call('cron.forceBoardMigrationScan', (error, result) => {
|
||||
if (error) {
|
||||
console.error('Failed to force board scan:', error);
|
||||
alert('Failed to force board scan: ' + error.message);
|
||||
} else {
|
||||
// Board scan started successfully
|
||||
// Refresh the data
|
||||
loadBoardOperations(Template.instance());
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Helper functions for cron settings
|
||||
function loadCronData(instance) {
|
||||
instance.loading.set(true);
|
||||
|
||||
// Load migration progress
|
||||
Meteor.call('cron.getMigrationProgress', (error, result) => {
|
||||
if (result) {
|
||||
migrationProgress.set(result.progress);
|
||||
migrationStatus.set(result.status);
|
||||
migrationCurrentStep.set(result.currentStep);
|
||||
migrationSteps.set(result.steps);
|
||||
isMigrating.set(result.isMigrating);
|
||||
}
|
||||
});
|
||||
|
||||
// Load cron jobs
|
||||
loadCronJobs(instance);
|
||||
|
||||
instance.loading.set(false);
|
||||
}
|
||||
|
||||
function loadCronJobs(instance) {
|
||||
Meteor.call('cron.getJobs', (error, result) => {
|
||||
if (result) {
|
||||
cronJobs.set(result);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function loadBoardOperations(instance) {
|
||||
const page = instance.currentPage.get();
|
||||
const limit = instance.pageSize.get();
|
||||
const searchTerm = instance.searchTerm.get();
|
||||
|
||||
Meteor.call('cron.getAllBoardOperations', page, limit, searchTerm, (error, result) => {
|
||||
if (result) {
|
||||
instance.boardOperations.set(result.operations);
|
||||
instance.pagination.set({
|
||||
total: result.total,
|
||||
page: result.page,
|
||||
limit: result.limit,
|
||||
totalPages: result.totalPages,
|
||||
start: ((result.page - 1) * result.limit) + 1,
|
||||
end: Math.min(result.page * result.limit, result.total),
|
||||
hasPrev: result.page > 1,
|
||||
hasNext: result.page < result.totalPages
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Load operation stats
|
||||
Meteor.call('cron.getBoardOperationStats', (error, result) => {
|
||||
if (result) {
|
||||
instance.operationStats.set(result);
|
||||
}
|
||||
});
|
||||
|
||||
// Load queue stats
|
||||
Meteor.call('cron.getQueueStats', (error, result) => {
|
||||
if (result) {
|
||||
instance.queueStats.set(result);
|
||||
}
|
||||
});
|
||||
|
||||
// Load system resources
|
||||
Meteor.call('cron.getSystemResources', (error, result) => {
|
||||
if (result) {
|
||||
instance.systemResources.set(result);
|
||||
}
|
||||
});
|
||||
|
||||
// Load board migration stats
|
||||
Meteor.call('cron.getBoardMigrationStats', (error, result) => {
|
||||
if (result) {
|
||||
instance.boardMigrationStats.set(result);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function pollMigrationProgress(instance) {
|
||||
const pollInterval = setInterval(() => {
|
||||
Meteor.call('cron.getMigrationProgress', (error, result) => {
|
||||
if (result) {
|
||||
migrationProgress.set(result.progress);
|
||||
migrationStatus.set(result.status);
|
||||
migrationCurrentStep.set(result.currentStep);
|
||||
migrationSteps.set(result.steps);
|
||||
isMigrating.set(result.isMigrating);
|
||||
|
||||
// Stop polling if migration is complete
|
||||
if (!result.isMigrating && result.progress === 100) {
|
||||
clearInterval(pollInterval);
|
||||
}
|
||||
}
|
||||
});
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
// Template helpers for cronSettings
|
||||
Template.cronSettings.helpers({
|
||||
loading() {
|
||||
const instance = Template.instance();
|
||||
return instance.loading && instance.loading.get();
|
||||
},
|
||||
showMigrations() {
|
||||
const instance = Template.instance();
|
||||
return instance.showMigrations && instance.showMigrations.get();
|
||||
},
|
||||
showBoardOperations() {
|
||||
const instance = Template.instance();
|
||||
return instance.showBoardOperations && instance.showBoardOperations.get();
|
||||
},
|
||||
showJobs() {
|
||||
const instance = Template.instance();
|
||||
return instance.showJobs && instance.showJobs.get();
|
||||
},
|
||||
showAddJob() {
|
||||
const instance = Template.instance();
|
||||
return instance.showAddJob && instance.showAddJob.get();
|
||||
},
|
||||
});
|
||||
|
|
@ -4,115 +4,224 @@ template(name="setting")
|
|||
| {{_ 'error-notAuthorized'}}
|
||||
else
|
||||
.content-title.ext-box
|
||||
if loading.get
|
||||
+spinner
|
||||
else if generalSetting.get
|
||||
if isGeneralSetting
|
||||
span
|
||||
i.fa.fa-sign-in
|
||||
| {{_ 'registration'}}
|
||||
else if emailSetting.get
|
||||
else if isEmailSetting
|
||||
span
|
||||
i.fa.fa-envelope
|
||||
| {{_ 'email'}}
|
||||
else if accountSetting.get
|
||||
else if isAccountSetting
|
||||
span
|
||||
i.fa.fa-users
|
||||
| {{_ 'accounts'}}
|
||||
else if tableVisibilityModeSetting.get
|
||||
else if isTableVisibilityModeSetting
|
||||
span
|
||||
i.fa.fa-eye
|
||||
| {{_ 'tableVisibilityMode'}}
|
||||
else if announcementSetting.get
|
||||
else if isAnnouncementSetting
|
||||
span
|
||||
i.fa.fa-bullhorn
|
||||
| {{_ 'admin-announcement'}}
|
||||
else if accessibilitySetting.get
|
||||
else if isAccessibilitySetting
|
||||
span
|
||||
i.fa.fa-universal-access
|
||||
| {{_ 'accessibility'}}
|
||||
else if layoutSetting.get
|
||||
else if isLayoutSetting
|
||||
span
|
||||
i.fa.fa-object-group
|
||||
| {{_ 'layout'}}
|
||||
else if webhookSetting.get
|
||||
else if isWebhookSetting
|
||||
span
|
||||
i.fa.fa-globe
|
||||
| {{_ 'global-webhook'}}
|
||||
else if attachmentSettings.get
|
||||
else if isAttachmentSettings
|
||||
span
|
||||
i.fa.fa-paperclip
|
||||
| {{_ 'attachments'}}
|
||||
else if cronSettings.get
|
||||
else if isCronSettings
|
||||
span
|
||||
i.fa.fa-clock-o
|
||||
| {{_ 'cron'}}
|
||||
.content-body
|
||||
.side-menu
|
||||
ul
|
||||
li(class="{{#if generalSetting}}active{{/if}}")
|
||||
li(class="{{#if isGeneralSetting}}active{{/if}}")
|
||||
a.js-setting-menu(data-id="registration-setting")
|
||||
i.fa.fa-sign-in
|
||||
| {{_ 'registration'}}
|
||||
unless isSandstorm
|
||||
li(class="{{#if emailSetting}}active{{/if}}")
|
||||
li(class="{{#if isEmailSetting}}active{{/if}}")
|
||||
a.js-setting-menu(data-id="email-setting")
|
||||
i.fa.fa-envelope
|
||||
| {{_ 'email'}}
|
||||
li(class="{{#if accountSetting}}active{{/if}}")
|
||||
li(class="{{#if isAccountSetting}}active{{/if}}")
|
||||
a.js-setting-menu(data-id="account-setting")
|
||||
i.fa.fa-users
|
||||
| {{_ 'accounts'}}
|
||||
li(class="{{#if tableVisibilityModeSetting}}active{{/if}}")
|
||||
li(class="{{#if isTableVisibilityModeSetting}}active{{/if}}")
|
||||
a.js-setting-menu(data-id="tableVisibilityMode-setting")
|
||||
i.fa.fa-eye
|
||||
| {{_ 'tableVisibilityMode'}}
|
||||
li(class="{{#if announcementSetting}}active{{/if}}")
|
||||
li(class="{{#if isAnnouncementSetting}}active{{/if}}")
|
||||
a.js-setting-menu(data-id="announcement-setting")
|
||||
i.fa.fa-bullhorn
|
||||
| {{_ 'admin-announcement'}}
|
||||
li(class="{{#if accessibilitySetting}}active{{/if}}")
|
||||
li(class="{{#if isAccessibilitySetting}}active{{/if}}")
|
||||
a.js-setting-menu(data-id="accessibility-setting")
|
||||
i.fa.fa-universal-access
|
||||
| {{_ 'accessibility'}}
|
||||
li(class="{{#if layoutSetting}}active{{/if}}")
|
||||
li(class="{{#if isLayoutSetting}}active{{/if}}")
|
||||
a.js-setting-menu(data-id="layout-setting")
|
||||
i.fa.fa-object-group
|
||||
| {{_ 'layout'}}
|
||||
li(class="{{#if webhookSetting}}active{{/if}}")
|
||||
li(class="{{#if isWebhookSetting}}active{{/if}}")
|
||||
a.js-setting-menu(data-id="webhook-setting")
|
||||
i.fa.fa-globe
|
||||
| {{_ 'global-webhook'}}
|
||||
li(class="{{#if attachmentSettings}}active{{/if}}")
|
||||
li(class="{{#if isAttachmentSettings}}active{{/if}}")
|
||||
a.js-setting-menu(data-id="attachment-settings")
|
||||
i.fa.fa-paperclip
|
||||
| {{_ 'attachments'}}
|
||||
li(class="{{#if cronSettings}}active{{/if}}")
|
||||
li(class="{{#if isCronSettings}}active{{/if}}")
|
||||
a.js-setting-menu(data-id="cron-settings")
|
||||
i.fa.fa-clock-o
|
||||
| {{_ 'cron'}}
|
||||
.main-body
|
||||
if loading.get
|
||||
if isLoading
|
||||
+spinner
|
||||
else if attachmentSettings.get
|
||||
+attachmentSettings
|
||||
else if cronSettings.get
|
||||
+cronSettings
|
||||
else if generalSetting.get
|
||||
else if isAttachmentSettings
|
||||
ul#attachment-setting.setting-detail
|
||||
li
|
||||
h3 {{_ 'attachment-storage-configuration'}}
|
||||
.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'}}
|
||||
|
||||
li
|
||||
h3 {{_ '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'}}
|
||||
|
||||
li
|
||||
h3 {{_ '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'}}
|
||||
|
||||
.form-group
|
||||
button.js-test-s3-connection.btn.btn-secondary {{_ 'test-s3-connection'}}
|
||||
button.js-save-s3-settings.btn.btn-primary {{_ 'save-s3-settings'}}
|
||||
else if isCronSettings
|
||||
ul#cron-setting.setting-detail
|
||||
li
|
||||
h3 {{_ 'cron-migrations'}}
|
||||
.form-group
|
||||
label {{_ 'migration-status'}}
|
||||
.status-indicator
|
||||
span.status-label {{_ 'status'}}:
|
||||
span.status-value {{migrationStatus}}
|
||||
.progress-section
|
||||
.progress
|
||||
.progress-bar(role="progressbar" style="width: {{migrationProgress}}%" aria-valuenow="{{migrationProgress}}" aria-valuemin="0" aria-valuemax="100")
|
||||
| {{migrationProgress}}%
|
||||
.progress-text
|
||||
| {{migrationProgress}}% {{_ 'complete'}}
|
||||
|
||||
.form-group
|
||||
button.js-start-all-migrations.btn.btn-primary {{_ 'start-all-migrations'}}
|
||||
button.js-pause-all-migrations.btn.btn-warning {{_ 'pause-all-migrations'}}
|
||||
button.js-stop-all-migrations.btn.btn-danger {{_ 'stop-all-migrations'}}
|
||||
|
||||
li
|
||||
h3 {{_ 'board-operations'}}
|
||||
.form-group
|
||||
label {{_ 'scheduled-board-operations'}}
|
||||
button.js-schedule-board-cleanup.btn.btn-primary {{_ 'schedule-board-cleanup'}}
|
||||
button.js-schedule-board-archive.btn.btn-warning {{_ 'schedule-board-archive'}}
|
||||
button.js-schedule-board-backup.btn.btn-info {{_ 'schedule-board-backup'}}
|
||||
|
||||
li
|
||||
h3 {{_ 'cron-jobs'}}
|
||||
.form-group
|
||||
label {{_ 'active-cron-jobs'}}
|
||||
each cronJobs
|
||||
.job-item
|
||||
.job-info
|
||||
.job-name {{name}}
|
||||
.job-schedule {{schedule}}
|
||||
.job-description {{description}}
|
||||
.job-actions
|
||||
button.js-pause-job.btn.btn-sm.btn-warning(data-job-id="{{_id}}") {{_ 'pause'}}
|
||||
button.js-delete-job.btn.btn-sm.btn-danger(data-job-id="{{_id}}") {{_ 'delete'}}
|
||||
.add-job-section
|
||||
button.js-add-cron-job.btn.btn-success {{_ 'add-cron-job'}}
|
||||
else if isGeneralSetting
|
||||
+general
|
||||
else if emailSetting.get
|
||||
else if isEmailSetting
|
||||
unless isSandstorm
|
||||
+email
|
||||
else if accountSetting.get
|
||||
else if isAccountSetting
|
||||
+accountSettings
|
||||
else if tableVisibilityModeSetting.get
|
||||
else if isTableVisibilityModeSetting
|
||||
+tableVisibilityModeSettings
|
||||
else if announcementSetting.get
|
||||
else if isAnnouncementSetting
|
||||
+announcementSettings
|
||||
else if accessibilitySetting.get
|
||||
else if isAccessibilitySetting
|
||||
+accessibilitySettings
|
||||
else if layoutSetting.get
|
||||
else if isLayoutSetting
|
||||
+layoutSettings
|
||||
else if webhookSetting.get
|
||||
else if isWebhookSetting
|
||||
+webhookSettings
|
||||
|
||||
template(name="webhookSettings")
|
||||
|
|
|
|||
|
|
@ -34,11 +34,239 @@ BlazeComponent.extendComponent({
|
|||
setError(error) {
|
||||
this.error.set(error);
|
||||
},
|
||||
|
||||
// Template helpers moved to BlazeComponent - using different names to avoid conflicts
|
||||
isGeneralSetting() {
|
||||
return this.generalSetting && this.generalSetting.get();
|
||||
},
|
||||
isEmailSetting() {
|
||||
return this.emailSetting && this.emailSetting.get();
|
||||
},
|
||||
isAccountSetting() {
|
||||
return this.accountSetting && this.accountSetting.get();
|
||||
},
|
||||
isTableVisibilityModeSetting() {
|
||||
return this.tableVisibilityModeSetting && this.tableVisibilityModeSetting.get();
|
||||
},
|
||||
isAnnouncementSetting() {
|
||||
return this.announcementSetting && this.announcementSetting.get();
|
||||
},
|
||||
isAccessibilitySetting() {
|
||||
return this.accessibilitySetting && this.accessibilitySetting.get();
|
||||
},
|
||||
isLayoutSetting() {
|
||||
return this.layoutSetting && this.layoutSetting.get();
|
||||
},
|
||||
isWebhookSetting() {
|
||||
return this.webhookSetting && this.webhookSetting.get();
|
||||
},
|
||||
isAttachmentSettings() {
|
||||
return this.attachmentSettings && this.attachmentSettings.get();
|
||||
},
|
||||
isCronSettings() {
|
||||
return this.cronSettings && this.cronSettings.get();
|
||||
},
|
||||
isLoading() {
|
||||
return this.loading && this.loading.get();
|
||||
},
|
||||
|
||||
// Attachment settings helpers
|
||||
filesystemPath() {
|
||||
return process.env.WRITABLE_PATH || '/data';
|
||||
},
|
||||
|
||||
attachmentsPath() {
|
||||
const writablePath = process.env.WRITABLE_PATH || '/data';
|
||||
return `${writablePath}/attachments`;
|
||||
},
|
||||
|
||||
avatarsPath() {
|
||||
const writablePath = process.env.WRITABLE_PATH || '/data';
|
||||
return `${writablePath}/avatars`;
|
||||
},
|
||||
|
||||
gridfsEnabled() {
|
||||
return process.env.GRIDFS_ENABLED === 'true';
|
||||
},
|
||||
|
||||
s3Enabled() {
|
||||
return process.env.S3_ENABLED === 'true';
|
||||
},
|
||||
|
||||
s3Endpoint() {
|
||||
return process.env.S3_ENDPOINT || '';
|
||||
},
|
||||
|
||||
s3Bucket() {
|
||||
return process.env.S3_BUCKET || '';
|
||||
},
|
||||
|
||||
s3Region() {
|
||||
return process.env.S3_REGION || '';
|
||||
},
|
||||
|
||||
s3SslEnabled() {
|
||||
return process.env.S3_SSL_ENABLED === 'true';
|
||||
},
|
||||
|
||||
s3Port() {
|
||||
return process.env.S3_PORT || 443;
|
||||
},
|
||||
|
||||
// Cron settings helpers
|
||||
migrationStatus() {
|
||||
return 'idle'; // Placeholder
|
||||
},
|
||||
|
||||
migrationProgress() {
|
||||
return 0; // Placeholder
|
||||
},
|
||||
|
||||
cronJobs() {
|
||||
return []; // Placeholder
|
||||
},
|
||||
|
||||
setLoading(w) {
|
||||
this.loading.set(w);
|
||||
},
|
||||
|
||||
// Event handlers for attachment settings
|
||||
'click button.js-test-s3-connection'(event) {
|
||||
event.preventDefault();
|
||||
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'));
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
'click button.js-save-s3-settings'(event) {
|
||||
event.preventDefault();
|
||||
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
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// Event handlers for cron settings
|
||||
'click button.js-start-all-migrations'(event) {
|
||||
event.preventDefault();
|
||||
Meteor.call('startAllMigrations', (error, result) => {
|
||||
if (error) {
|
||||
alert(TAPi18n.__('migration-start-failed') + ': ' + error.reason);
|
||||
} else {
|
||||
alert(TAPi18n.__('migration-started'));
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
'click button.js-pause-all-migrations'(event) {
|
||||
event.preventDefault();
|
||||
Meteor.call('pauseAllMigrations', (error, result) => {
|
||||
if (error) {
|
||||
alert(TAPi18n.__('migration-pause-failed') + ': ' + error.reason);
|
||||
} else {
|
||||
alert(TAPi18n.__('migration-paused'));
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
'click button.js-stop-all-migrations'(event) {
|
||||
event.preventDefault();
|
||||
if (confirm(TAPi18n.__('migration-stop-confirm'))) {
|
||||
Meteor.call('stopAllMigrations', (error, result) => {
|
||||
if (error) {
|
||||
alert(TAPi18n.__('migration-stop-failed') + ': ' + error.reason);
|
||||
} else {
|
||||
alert(TAPi18n.__('migration-stopped'));
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
'click button.js-schedule-board-cleanup'(event) {
|
||||
event.preventDefault();
|
||||
Meteor.call('scheduleBoardCleanup', (error, result) => {
|
||||
if (error) {
|
||||
alert(TAPi18n.__('board-cleanup-failed') + ': ' + error.reason);
|
||||
} else {
|
||||
alert(TAPi18n.__('board-cleanup-scheduled'));
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
'click button.js-schedule-board-archive'(event) {
|
||||
event.preventDefault();
|
||||
Meteor.call('scheduleBoardArchive', (error, result) => {
|
||||
if (error) {
|
||||
alert(TAPi18n.__('board-archive-failed') + ': ' + error.reason);
|
||||
} else {
|
||||
alert(TAPi18n.__('board-archive-scheduled'));
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
'click button.js-schedule-board-backup'(event) {
|
||||
event.preventDefault();
|
||||
Meteor.call('scheduleBoardBackup', (error, result) => {
|
||||
if (error) {
|
||||
alert(TAPi18n.__('board-backup-failed') + ': ' + error.reason);
|
||||
} else {
|
||||
alert(TAPi18n.__('board-backup-scheduled'));
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
'click button.js-pause-job'(event) {
|
||||
event.preventDefault();
|
||||
const jobId = $(event.target).data('job-id');
|
||||
Meteor.call('pauseCronJob', jobId, (error, result) => {
|
||||
if (error) {
|
||||
alert(TAPi18n.__('cron-job-pause-failed') + ': ' + error.reason);
|
||||
} else {
|
||||
alert(TAPi18n.__('cron-job-paused'));
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
'click button.js-delete-job'(event) {
|
||||
event.preventDefault();
|
||||
const jobId = $(event.target).data('job-id');
|
||||
if (confirm(TAPi18n.__('cron-job-delete-confirm'))) {
|
||||
Meteor.call('deleteCronJob', jobId, (error, result) => {
|
||||
if (error) {
|
||||
alert(TAPi18n.__('cron-job-delete-failed') + ': ' + error.reason);
|
||||
} else {
|
||||
alert(TAPi18n.__('cron-job-deleted'));
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
'click button.js-add-cron-job'(event) {
|
||||
event.preventDefault();
|
||||
// Placeholder for adding a new cron job (e.g., open a modal)
|
||||
alert(TAPi18n.__('add-cron-job-placeholder'));
|
||||
},
|
||||
|
||||
checkField(selector) {
|
||||
const value = $(selector).val();
|
||||
if (!value || value.trim() === '') {
|
||||
|
|
@ -104,37 +332,6 @@ BlazeComponent.extendComponent({
|
|||
$('#display-authentication-method').toggleClass('is-checked');
|
||||
},
|
||||
|
||||
switchAttachmentTab(event) {
|
||||
event.preventDefault();
|
||||
const target = $(event.target);
|
||||
const targetID = target.data('id');
|
||||
|
||||
// Update active tab
|
||||
$('.tab-nav li.active').removeClass('active');
|
||||
target.parent().addClass('active');
|
||||
|
||||
// Call the attachment settings component method if available
|
||||
if (window.attachmentSettings && window.attachmentSettings.switchMenu) {
|
||||
window.attachmentSettings.switchMenu(event, targetID);
|
||||
}
|
||||
},
|
||||
|
||||
switchCronTab(event) {
|
||||
event.preventDefault();
|
||||
const target = $(event.target);
|
||||
const targetID = target.data('id');
|
||||
|
||||
// Update active tab
|
||||
$('.tab-nav li.active').removeClass('active');
|
||||
target.parent().addClass('active');
|
||||
|
||||
// Call the cron settings template method if available
|
||||
const cronTemplate = Template.instance();
|
||||
if (cronTemplate && cronTemplate.switchMenu) {
|
||||
cronTemplate.switchMenu(event, targetID);
|
||||
}
|
||||
},
|
||||
|
||||
initializeAttachmentSubMenu() {
|
||||
// Set default sub-menu state for attachment settings
|
||||
// This will be handled by the attachment settings component
|
||||
|
|
@ -357,13 +554,6 @@ BlazeComponent.extendComponent({
|
|||
'click button.js-save-layout': this.saveLayout,
|
||||
'click a.js-toggle-display-authentication-method': this
|
||||
.toggleDisplayAuthenticationMethod,
|
||||
'click a.js-attachment-storage-settings': this.switchAttachmentTab,
|
||||
'click a.js-attachment-migration': this.switchAttachmentTab,
|
||||
'click a.js-attachment-monitoring': this.switchAttachmentTab,
|
||||
'click a.js-cron-migrations': this.switchCronTab,
|
||||
'click a.js-cron-board-operations': this.switchCronTab,
|
||||
'click a.js-cron-jobs': this.switchCronTab,
|
||||
'click a.js-cron-add': this.switchCronTab,
|
||||
},
|
||||
];
|
||||
},
|
||||
|
|
@ -609,50 +799,3 @@ Template.selectSpinnerName.helpers({
|
|||
},
|
||||
});
|
||||
|
||||
// Template helpers for the setting template
|
||||
Template.setting.helpers({
|
||||
generalSetting() {
|
||||
const instance = Template.instance();
|
||||
return instance.generalSetting && instance.generalSetting.get();
|
||||
},
|
||||
emailSetting() {
|
||||
const instance = Template.instance();
|
||||
return instance.emailSetting && instance.emailSetting.get();
|
||||
},
|
||||
accountSetting() {
|
||||
const instance = Template.instance();
|
||||
return instance.accountSetting && instance.accountSetting.get();
|
||||
},
|
||||
tableVisibilityModeSetting() {
|
||||
const instance = Template.instance();
|
||||
return instance.tableVisibilityModeSetting && instance.tableVisibilityModeSetting.get();
|
||||
},
|
||||
announcementSetting() {
|
||||
const instance = Template.instance();
|
||||
return instance.announcementSetting && instance.announcementSetting.get();
|
||||
},
|
||||
accessibilitySetting() {
|
||||
const instance = Template.instance();
|
||||
return instance.accessibilitySetting && instance.accessibilitySetting.get();
|
||||
},
|
||||
layoutSetting() {
|
||||
const instance = Template.instance();
|
||||
return instance.layoutSetting && instance.layoutSetting.get();
|
||||
},
|
||||
webhookSetting() {
|
||||
const instance = Template.instance();
|
||||
return instance.webhookSetting && instance.webhookSetting.get();
|
||||
},
|
||||
attachmentSettings() {
|
||||
const instance = Template.instance();
|
||||
return instance.attachmentSettings && instance.attachmentSettings.get();
|
||||
},
|
||||
cronSettings() {
|
||||
const instance = Template.instance();
|
||||
return instance.cronSettings && instance.cronSettings.get();
|
||||
},
|
||||
loading() {
|
||||
const instance = Template.instance();
|
||||
return instance.loading && instance.loading.get();
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,67 +0,0 @@
|
|||
/* Settings Tabs Styles */
|
||||
.settings-tabs {
|
||||
margin-bottom: 20px;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.tab-nav {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.tab-nav li {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.tab-nav li a {
|
||||
display: block;
|
||||
padding: 12px 20px;
|
||||
text-decoration: none;
|
||||
color: #666;
|
||||
border-bottom: 3px solid transparent;
|
||||
transition: all 0.3s ease;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.tab-nav li a:hover {
|
||||
color: #333;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.tab-nav li.active a {
|
||||
color: #2196F3;
|
||||
border-bottom-color: #2196F3;
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.tab-nav li a i {
|
||||
margin-right: 8px;
|
||||
width: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
padding: 20px 0;
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
/* Responsive design */
|
||||
@media (max-width: 768px) {
|
||||
.tab-nav {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.tab-nav li a {
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.tab-nav li.active a {
|
||||
border-bottom-color: #2196F3;
|
||||
border-right: 3px solid #2196F3;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue